Search Unity

Simulation and Rendering in Different Worlds

Discussion in 'Graphics for ECS' started by PublicEnumE, Feb 10, 2019.

  1. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    At the last Unite, @Joachim_Ante talked about one potential use of being able to create multiple Worlds: running your game's simulation and rendering in different worlds, at different frame-rates.

    Since then, the team has released support for multiple Worlds. But I haven't be able to find much info about controlling the rendering pipeline, or running these worlds at different frame-rates.

    I would love to find out more information about how this should be done. Can anyone point to information or examples out there that I may be missing? Many thanks!
     
    learc83 and MostHated like this.
  2. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    I would be grateful to anyone with info on this topic. :)
     
    Last edited: Feb 11, 2019
  3. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    1,235
    I am with you there. The streaming aspect of the Mega City demo (which utilizes what you are asking about) is something I can't wait to see some more info on.
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    They already released the scene conversion and the world methods you need to stream them in the most recent version of ecs; it's wonderful. It's not the same as using a separate world for simulation though.

    To do scene conversion just call

    GameObjectConversionUtility.ConvertScene(Scene scene, World dstEntityWorld);


    That's really all there is to it. It's not really a 1 click solution yet, but doesn't take a lot to get working. My specific scene conversion looks like this. It also grabs the baked NavMesh and I'll probably add baked lightmaps at some point when I need it.

    Code (CSharp):
    1. // <copyright file="SceneConversion.cs" company="BovineLabs">
    2. //     Copyright (c) BovineLabs. All rights reserved.
    3. // </copyright>
    4. #if UNITY_EDITOR
    5.  
    6. namespace BovineLabs.Common.Utility
    7. {
    8.     using System.IO;
    9.     using Unity.Entities;
    10.     using Unity.Entities.Serialization;
    11.     using UnityEditor;
    12.     using UnityEngine;
    13.     using UnityEngine.AI;
    14.     using UnityEngine.Assertions;
    15.     using UnityEngine.SceneManagement;
    16.  
    17.     /// <summary>
    18.     /// Tools for converting a scene to ECS representation.
    19.     /// </summary>
    20.     public static class SceneConversion
    21.     {
    22.         private const string ParentFolder = "Assets/Prefabs";
    23.         private const string Folder = "Scenes";
    24.  
    25.         /// <summary>
    26.         /// Get or Create a new scene directory.
    27.         /// </summary>
    28.         /// <param name="name">The name of the scene.</param>
    29.         /// <returns>The directory path.</returns>
    30.         public static string GetOrCreateDirectory(string name)
    31.         {
    32.             var directory = Path.Combine(ParentFolder, Folder);
    33.  
    34.             if (!AssetDatabase.IsValidFolder(directory))
    35.             {
    36.                 AssetDatabase.CreateFolder(ParentFolder, Folder);
    37.             }
    38.  
    39.             var sceneDirectory = Path.Combine(directory, name);
    40.  
    41.             if (!AssetDatabase.IsValidFolder(sceneDirectory))
    42.             {
    43.                 AssetDatabase.CreateFolder(directory, name);
    44.             }
    45.  
    46.             return sceneDirectory;
    47.         }
    48.  
    49.         /// <summary>
    50.         /// Convert a scene to ECS representation.
    51.         /// </summary>
    52.         /// <returns>The converted scene.</returns>
    53.         public static ConvertedScene Convert()
    54.         {
    55.             var scene = SceneManager.GetActiveScene();
    56.  
    57.             var directory = Path.Combine(ParentFolder, Folder);
    58.             var savePath = Path.Combine(directory, $"{scene.name}.asset");
    59.             var dataDirectory = GetOrCreateDirectory(scene.name);
    60.  
    61.             ConvertScene(scene, dataDirectory, out var sharedComponentPrefab, out var serializedData);
    62.             var navMeshData = GetNavMeshData(scene);
    63.  
    64.             var save = AssetDatabase.LoadAssetAtPath<ConvertedScene>(savePath);
    65.  
    66.             if (save == null)
    67.             {
    68.                 save = ScriptableObject.CreateInstance<ConvertedScene>();
    69.                 save.SetData(serializedData, sharedComponentPrefab, navMeshData);
    70.                 AssetDatabase.CreateAsset(save, savePath);
    71.             }
    72.             else
    73.             {
    74.                 save.SetData(serializedData, sharedComponentPrefab, navMeshData);
    75.                 EditorUtility.SetDirty(save);
    76.                 AssetDatabase.SaveAssets();
    77.             }
    78.  
    79.             return save;
    80.         }
    81.  
    82.         private static void ConvertScene(Scene scene, string dataDirectory, out GameObject sharedComponentPrefab, out TextAsset serializedData)
    83.         {
    84.             var world = new World("world");
    85.             GameObjectConversionUtility.ConvertScene(scene, world);
    86.  
    87.             var entityManager = world.GetOrCreateManager<EntityManager>();
    88.  
    89.             var writerPath = Path.Combine(Path.Combine(dataDirectory, $"{scene.name}.bytes"));
    90.             var prefabPath = Path.Combine(dataDirectory, $"{scene.name}_SharedComponents.prefab");
    91.  
    92.             sharedComponentPrefab = null;
    93.  
    94.             using (Unity.Entities.Serialization.BinaryWriter writer = new StreamBinaryWriter(writerPath))
    95.             {
    96.                 SerializeUtilityHybrid.Serialize(entityManager, writer, out var sharedComponents);
    97.  
    98.                 if (sharedComponents != null)
    99.                 {
    100.                     sharedComponentPrefab =
    101.                         PrefabUtility.SaveAsPrefabAsset(sharedComponents, prefabPath, out var success);
    102.                     Assert.IsTrue(success);
    103.                     Object.DestroyImmediate(sharedComponents);
    104.                 }
    105.             }
    106.  
    107.             AssetDatabase.Refresh();
    108.             serializedData = AssetDatabase.LoadAssetAtPath<TextAsset>(writerPath);
    109.  
    110.             Assert.IsNotNull(serializedData);
    111.  
    112.             world.Dispose();
    113.             WordStorage.Instance.Dispose();
    114.             WordStorage.Instance = null;
    115.         }
    116.  
    117.         private static NavMeshData GetNavMeshData(Scene scene)
    118.         {
    119.             var scenePath = Path.GetDirectoryName(scene.path);
    120.             Assert.IsNotNull(scenePath);
    121.  
    122.             var path = Path.Combine(scenePath, scene.name);
    123.             path = Path.Combine(path, "NavMesh.asset");
    124.             var navMeshData = AssetDatabase.LoadAssetAtPath<NavMeshData>(path);
    125.             return navMeshData;
    126.         }
    127.     }
    128. }
    129. #endif
     
  5. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    1,235
    Oh! Nice! I can't wait to check more into this tomorrow. : D
     
  6. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    You can also add custom conversions by implementing your own GameObjectConversionSystem

    For example, if I want to convert UnityEngine.AI.NavMeshLink to an ECS version I created

    Code (CSharp):
    1. // <copyright file="NavMeshLinkConversion.cs" company="BovineLabs">
    2. //     Copyright (c) BovineLabs. All rights reserved.
    3. // </copyright>
    4.  
    5. namespace BovineLabs.Systems.Pathfinding
    6. {
    7.     using UnityEngine;
    8.  
    9.     /// <summary>
    10.     /// Converts UnityEngine.AI.NavMeshLink to ECS presentation.
    11.     /// </summary>
    12.     public class NavMeshLinkConversion : GameObjectConversionSystem
    13.     {
    14.         /// <inheritdoc />
    15.         protected override void OnUpdate()
    16.         {
    17.             this.ForEach((UnityEngine.AI.NavMeshLink navMeshLink) =>
    18.             {
    19.                 var entity = this.GetPrimaryEntity(navMeshLink);
    20.  
    21.                 var data = new NavMeshLink
    22.                 {
    23.                     AgentTypeID = navMeshLink.agentTypeID,
    24.                     StartPosition = navMeshLink.startPoint,
    25.                     EndPosition = navMeshLink.endPoint,
    26.                     CostModifier = navMeshLink.costModifier,
    27.                     Width = navMeshLink.width,
    28.                     Bidirectional = navMeshLink.bidirectional,
    29.                     Area = navMeshLink.area,
    30.                 };
    31.  
    32.                 this.DstEntityManager.AddComponentData(entity, data);
    33.             });
    34.         }
    35.     }
    36. }
     
    The5 and MostHated like this.
  7. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    1,235
    Nice, that is some good stuff.
     
  8. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Should add, all you need to do to load is specify the entity manager you want to load the entities into.

    Code (CSharp):
    1.         public void Deserialize(EntityManager entityManager)
    2.         {
    3.             using (BinaryReader reader = new TextAssetReader(this.world))
    4.             {
    5.                 SerializeUtilityHybrid.Deserialize(entityManager, reader, this.sharedComponentData);
    6.             }
    7.         }
     
    MostHated likes this.
  9. learc83

    learc83

    Joined:
    Feb 28, 2018
    Posts:
    41
    I would also love to see an example of the 2 worlds syasys. At a high level, what is the mechanism for passing data between worlds?

    In the simplest case, say I have some spheres that only have positions and rendermeshes, and all they do is move horizontally.

    I update their positions in the simulation world...what next?
     
    MostHated and PublicEnumE like this.
  10. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    I would be interested to know if rendering can be delayed in one world, until a second world says it's allowed to proceed.

    Another way to say that: I want to run my simulation loop a variable number of times each frame. And for the game to only render the results when the sim loop has decided it's done making changes.

    That might sound like a strange thing to want, but it could be helpful in at least one case I'm looking at (though unfortunately I can't give details).

    But regardless, I would just love to hear more about this feature, even if they don't have what I want. Since the knowledge doesn't seem to be widespread in the community, and since no Unity devs have commented here, it may be that we could hear more next month. :)
     
  11. Vuh-Hans

    Vuh-Hans

    Joined:
    Sep 24, 2013
    Posts:
    42
    I'm been dabbling a bit on the two world (Sim/Pres) concept, and the biggest limitation I've found is synchronization between them. The only way I could find to do so is through what other networking approaches out there are doing (except without the serialization part).

    The basic gist is that you need a way to quickly copy component/entity data (preferably in a generic way, and preferably only things that have changed) from one world to the other, and you need to do it at a specific synchronization point where both worlds are "still" since otherwise there might be race conditions between the worlds (if you are using jobs on any of them). In my case, I have a simulation world that operates at 20Hz in a semi-asynchronous manner (most things are done in jobs that are not limited to one frame, so the sim tick's workload is distributed across many frames), and whenever a simulation tick finishes I need to synchronize data into the presentation world, which is updated along the actual framerate.

    For example, my physics happens on a sim tick and calculates new positions for things that are moving. On the presentation world (which is hybrid ECS with game objects) the position of things changes each frame based on interpolation between the last sim tick's results and the previous position. I copy the position from the sim world into the presentation world when the sim tick finishes. The presentation world sends queued up commands to the sim. It's very similar to an authoritative networked game, except instead of sending serialized updates you could memory copy directly between worlds. However, I haven't found an easy and fast way to do it yet. Closest I got was using generic methods created through reflection that added/removed entities and copied components from one world to the other having a reference to both.
     
    e199 likes this.
  12. jdtec

    jdtec

    Joined:
    Oct 25, 2017
    Posts:
    302
    I'm doing something very similar to Hans-D. I have a Sim and View world. The Sim world creates output entities that get copied into and processed by the View world.

    I haven't hit sync issues or anything like that yet but I'm carefully controlling quite a few things atm such as the update order of systems.

    I'm also waiting on cleaner & more efficient ways to pass data between worlds.
     
  13. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Resurrecting this old thread, because it’s still difficult to find much information about how to use multiple worlds.

    I’ve been searching for explanations or examples for the past few days. But while several people seem to be using multiple worlds, there doesn’t seem to be much in the way of documentation or code examples.

    @jdtec, in a scenario like the one you described, where are you storing the world variables for your sim and presentation worlds? Are you creating worlds, and calling Update() on them manually? Are you currently moving your entities from your Sim world to your Presentation world using EntityManager.CopyEntitiesFrom()?
     
    Last edited: Dec 25, 2019
  14. jdtec

    jdtec

    Joined:
    Oct 25, 2017
    Posts:
    302
    Happy New Year!

    I have a monobehaviour that stores each world created by manual bootstrap and calls update on the systems for each of the worlds from Update or FixedUpdate.

    I tried using some of the various EntityManager functions to copy entities but ran into problems or just didn't find it performant. That could be down to my own lack of understanding about how to do it best for sync points etc.

    I found it more straight forward to just use simple double buffered arrays (no entities) for reading/writing sim world outputs to the view world. The view world accesses the unlocked read buffer while the sim writes to the other buffer. I've kept it designed in a way that the double buffer method could be replaced to send data over a network instead of just a straight copy so that there is potential to have the sim world run on a separate machine in the future.

    I would guess the reason there are few examples is because to achieve it currently you have to hack things together a bit. It might stay this way until DOTs comes out of preview at which point I might see if there is a reason to move to a more official way of doing things.
     
  15. Thygrrr

    Thygrrr

    Joined:
    Sep 23, 2013
    Posts:
    700
    Do you have an example? How do you turn your array data back into entities?
     
  16. jdtec

    jdtec

    Joined:
    Oct 25, 2017
    Posts:
    302
    I simply wrote output data (eg. unit id, new position) to an array. I would then read this array from a system in the other world, lookup the corresponding entity with the same unit id and apply whatever data changes were required. Nothing that smart or using any less well known APIs.

    I have since stopped having separate worlds as it was slowing prototyping dev speed for unknown future gain. Originally there were plans to have one world as the server and the other as a client view/presentation layer. I merged the worlds to benefit from having the same entities with all data attached and skip the data transfer layer. Now I just update the presentation and simulation systems (in the same world) at different frequencies.
     
    sunrisebacon and Thygrrr like this.