Search Unity

Custom bootstrap and separate assemblies in ECS?

Discussion in 'Entity Component System' started by illinar, May 15, 2018.

  1. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    I have client and server in my project. I need custom bootstrap for Client and Server scenes in two custom builds.

    1. How to do custom bootstrap? I can't find any examples in the current samples.

    2. I have two-tree builds in my project (Client, Server, Terminal). They are in separate dlls. If I properly use custom bootstrap will only selected assemblies be included? (Assemblies used in bootstrap and their dependencies.)

    gqwegqw.PNG
     
  2. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    Can anyone explain how to create a world with specific systems instead of automatically generated one?
     
  3. GerardAtGrendel

    GerardAtGrendel

    Joined:
    Nov 30, 2017
    Posts:
    4
    If you use the #define directives UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP the default world is not created. (This is handled in AutomaticWorldBootstrap.cs)

    In DefaultWorldInitialization.cs you can see how you can add your own World and add systems to it. Basicly:
    Code (CSharp):
    1. World world = new World(worldName);
    2. world.GetOrCreateManager<CustomComponentSystem>();
    I haven't tried this myself yet. There is some other code there that might be necesary (like the call to ScriptBehaviourUpdateOrder.UpdatePlayerLoop(world); )
     
  4. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    Thank you. DefaultWorldInitialization.cs AutomaticWorldBootstrap.cs How can I find those?

    I still can't figure out how to use
    public T CreateManager<T>(params object[] constructorArgumnents) where T : ScriptBehaviourManager;


    Why it takes one system as T or EntityManager. It does create that one system, it works, but how do I add more systems? object[] is not a list of systems as I thought. What constructor arguments?
     
    Last edited: May 18, 2018
  5. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    I really hoped I can specify update groups that will be used, so I don't have to list all the systems. Or somehow just tell the world which assemblies to use or exclude. Same in the build, I need to exclude certain assemblies.
     
  6. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    NEED HELP still, please. How to add systems to a world?
     
  7. JooleanLogic

    JooleanLogic

    Joined:
    Mar 1, 2018
    Posts:
    447
    On my computer, DefaultWorldInitialization.cs is in C:\ProgramData\Unity\cache\packages\staging-packages.unity.com\com.unity.entities@0.0.11\Unity.Entities.Hybrid\Injection

    System registration is all laid out in the Initialize function. It iterates assemblies and calls World.GetOrCreateManager(type); for each system.
     
  8. vanxining

    vanxining

    Joined:
    Mar 29, 2018
    Posts:
    20
    Every system is a child class of ScriptBehaviourManager. (Yes, the EntityManager is also a system.)
    The naming is very weird. (System and Manager are the same?)
     
  9. MichalBUnity

    MichalBUnity

    Unity Technologies

    Joined:
    May 9, 2016
    Posts:
    18
    So i tried to do something similar not long ago and i ended up doing something like this;

    https://gist.github.com/michalbrzozowski/1c2ee6f9abcae055664847ad1e8e4365

    The plan was to create a Client and Server representation using worlds. Client world will receive data and Render, Server world will push simulation forward and push updates to the client to process. It has 3 possible sku's. Standalone Client, Standalone Server and Local (Client+Server).

    So to get this up and running you will need to do the following:

    1. Disable automatic world generation:
    Set the UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP preprocessor define.
    You can add this under:
    Edit -> Project Settings -> Player: Scripting Define Symbols

    2. Setup your own Initialize method

    3. Load your own worlds. Write Initialize and Shutdown functions for the PlayerLoop
    One approach is to start by dividing the code into 2 flows one for Client and one for Server, and seperate them by namespace (there are tons of ways to do this, i just chose namespace to try the whole flow out).

    4. Profit?

    One thing i noticed is that if you try to use TransformAccessArray or ComponentArray in your code you will most likely need to internalize the entities package and make sure you make the Hooks you need are public. (On line 34 inside Initialize in the DefaultWorldInitialization.cs you can see we are adding the hooks.)
     
    Jonathan_L likes this.
  10. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    Awesome. Everything works. Except for hooks (will try later).

    Thank you, everyone. @MichalBUnity that was exceptionally useful.

    I modified it a bit for my project. I use ClientBootstrap and ServerBootstrap Monobehaviours in Client and Server scenes.
    Code (CSharp):
    1. using System;
    2. using System.Linq;
    3. using UnityEngine;
    4. using Unity.Entities;
    5.  
    6. public class ClientBootstrap : MonoBehaviour
    7. {
    8.     public static World world;
    9.  
    10.     void Start()
    11.     {
    12.         world = new World("Client");
    13.         WorldCreator.AddSystemsFromAssemblies(world, "Client", "Common");
    14.         World.Active = null;
    15.         ScriptBehaviourUpdateOrder.UpdatePlayerLoop(world);
    16.     }
    17.  
    18.     void OnApplicationQuit()
    19.     {
    20.         world.Dispose();
    21.         ScriptBehaviourUpdateOrder.UpdatePlayerLoop(); // No idea what this line does.
    22.     }
    23. }
    And I modified WorldCreator to make it create worlds from assemblies.
    Code (CSharp):
    1. using System;
    2. using System.Linq;
    3. using UnityEngine;
    4. using Unity.Entities;
    5.  
    6. public class WorldCreator
    7. {
    8.     public static void AddSystemsFromAssemblies(World world, params string[] assemblyNames)
    9.     {
    10.         World.Active = world;
    11.  
    12.         foreach (var ass in AppDomain.CurrentDomain.GetAssemblies())
    13.         {
    14.             foreach (var n in assemblyNames)
    15.             {
    16.                 if (ass.GetName().Name == n)
    17.                 {
    18.                     if (ass.ManifestModule.ToString() == "Microsoft.CodeAnalysis.Scripting.dll")
    19.                         continue;
    20.                     var allTypes = ass.GetTypes();
    21.  
    22.                     var systemTypes = allTypes.Where(
    23.                         t => t.IsSubclassOf(typeof(ComponentSystemBase)) &&
    24.                         !t.IsAbstract &&
    25.                         !t.ContainsGenericParameters &&
    26.                         t.GetCustomAttributes(typeof(DisableAutoCreationAttribute), true).Length == 0);
    27.                     foreach (var type in systemTypes)
    28.                     {
    29.                         GetBehaviourManagerAndLogException(world, type);
    30.                     }
    31.                     break;
    32.                 }
    33.             }
    34.         }
    35.     }
    36.  
    37.     public static void GetBehaviourManagerAndLogException(World world, Type type)
    38.     {
    39.         try
    40.         {
    41.             // Debug.Log("Found System" + type.FullName);
    42.             world.GetOrCreateManager(type);
    43.         }
    44.         catch (Exception e)
    45.         {
    46.             Debug.LogException(e);
    47.         }
    48.     }
    49. }
    You can guess what ServerBootstrap looks like. But my BuildPipeline.cs might be useful for some people. I use it to build client and server separately:
    Code (CSharp):
    1. public class BuildPipepine
    2. {
    3.     [MenuItem("Build/BuildAll")]
    4.     public static void BuildAll()
    5.     {
    6.         BuildServer();
    7.         BuildClient();
    8.     }
    9.  
    10.     [MenuItem("Build/BuildServer")]
    11.     public static void BuildServer()
    12.     {
    13.         BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
    14.         buildPlayerOptions.scenes = new[] { "Assets/Scenes/Server.unity" };
    15.         buildPlayerOptions.locationPathName = "Builds/Server/Server.exe";
    16.         buildPlayerOptions.target = BuildTarget.StandaloneWindows64;
    17.         buildPlayerOptions.options = BuildOptions.EnableHeadlessMode;
    18.         BuildPipeline.BuildPlayer(buildPlayerOptions);
    19.     }
    20.  
    21.     [MenuItem("Build/BuildClient")]
    22.     public static void BuildClient()
    23.     {
    24.         BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
    25.         buildPlayerOptions.scenes = new[] { "Assets/Scenes/Client.unity" };
    26.         buildPlayerOptions.locationPathName = "Builds/Client/Client.exe";
    27.         buildPlayerOptions.target = BuildTarget.StandaloneWindows64;
    28.         BuildPipeline.BuildPlayer(buildPlayerOptions);
    29.     }
    30. }
     
    Last edited: May 22, 2018
  11. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    In case I might need it in the future, where does
    PlayerLoopManager
    come from in your example? @MichalBUnity
    PlayerLoopManager.RegisterDomainUnload(DomainUnloadShutdown);

    My compiler can't find it.
     
  12. GabrieleUnity

    GabrieleUnity

    Unity Technologies

    Joined:
    Sep 4, 2012
    Posts:
    116
    @illinar it should be in `Unity.Entities`
     
  13. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    @GabrieleUnity weird. My version is the latest available and it doesn't see that class.
     
  14. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    Trying to inject ComponentArray<> with no luck. How can I use hooks in a custom bootstrap code if those are internal classes?
    1. InjectionHookSupport.RegisterHook(new GameObjectArrayInjectionHook());
    2. InjectionHookSupport.RegisterHook(new TransformAccessArrayInjectionHook());
    3. InjectionHookSupport.RegisterHook(new ComponentArrayInjectionHook());
     
    T-Zee likes this.