Search Unity

How to Instantiate() GameObjects in a Job?

Discussion in 'Entity Component System' started by neounix, Jun 18, 2018.

  1. neounix

    neounix

    Joined:
    Jan 9, 2017
    Posts:
    28
    Well, I'm lost trying to convert a Project to use the new Jobs system even after watching many YT tutorials and reading some Job examples and docs.

    In a nutshell, I currently have a for loop which Instantiates a couple of hundred thousands GOs and it is very slow running in a single thread. So, I am now on my 12-core machine (MacPro, 2013 12-core) and trying to take the original for loop and convert it to a new Unity Job (step one) but I cannot get the MonoBehaviour Instantiate method to work in my IJobParallelFor.

    I read that the Jobs system should work in a hybrid mode with Mono without ECS (yet); but I cannot find any working examples which Instantiate GOs, so I'm stuck!

    Sidebar: Just as I finally was able to program OK in Mono; I now have to complete forget about MonoBehaviour if I want to use the Jobs system, isn't that right? Basically, I have to rewrite from scratch without any Mono?

    Or, Can I do as I am hoping to do, and still use Mono and the Job system together?

    If we are permitted to do this hybrid Mono-Jobs approach, can someone write someone who understand this write a C# code fragment that will Instantiate GOs in a Unity Job struct using IJobParallelFor?

    Beating my head up against the wall on this... LOL

    Thanks
     
    poritoshdavid likes this.
  2. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    You can't instantite GameObjects in job.
     
  3. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    The bridge to convert old GO Instantiate to hybrid ECS is EntityManager.Instantiate, which does not actually create any GO but Entity with the same components captured from your prefab argument. (Look in SpawnRandomCircleSystem example. Also main thread only I think but it is fast in batch) Or if you want to do it one more step back from ECS hybrid way you use old instantiate but attach GameObjectEntity to the prefab so you get both GO and Entity with copied components.
     
    Macix97 likes this.
  4. neounix

    neounix

    Joined:
    Jan 9, 2017
    Posts:
    28
    Thanks for the replies.

    Seem that if I have a complex project with thousands of lines of code all written with GOs and Mono, it will be very difficult to covert all this code to Jobs; and that all the Mono work I did which took nearly a year+ was a waste of time since the performance is terrible in a single thread, and the entire project, build around Mono is now obsolete.

    Based on what I understand now, the way ahead is to rewrite this entire project from scratch using Jobs and ECS.

    My GOD.... LOL I wasted an entire year of my life learning and writing obsolete Unity MonoBehavior code.

    Seems to (dumb) me it would have been better to create a Jobs system which works with GOs and MonoBehaviour as an intermediate Unity upgrade step versus developing "a new way of coding" which obsoletes all existing Unity MonoBehaviour code!

    As the old saying goes "the enemy of good is great" ... and it seems that existing GOs and other MonoBehaviour should work simply with a C# jobs system, but what do I know?!

    LOL... what a waste of a good year+ of my life!
     
    Last edited: Jun 18, 2018
    Gamer98898 likes this.
  5. Devilbox-Games

    Devilbox-Games

    Joined:
    Jul 3, 2012
    Posts:
    205
    GameObjects aren't obsolete. ECS is in beta and still not recommended for production yet. Even when it is "finished" it will still only be an optional, alternate way of working. Classic GameObjects & MonoBehaviors will still be a legitimate way of working for years to come.

    The Job system is also not tied to ECS. ECS is designed with the Job system in mind but you can use jobs without touching ECS at all. In fact the Job system was released as part of 2018.1 before ECS was easily usable without manually editing package files.

    If you've put so much effort in to a project using MonoBehavior already then it's not enirely recommended to try to shoehorn it into working with ECS. They're such different ways of working that it's never going to be particularly effective, you need to design your project with ECS in mind to make proper use of it.

    You can use Jobs with MonoBehaviors, you just have to work within the limitations of the two. GameObjects and MonoBehaviors are not thread safe, nor are most of the core Unity APIs, so making calls to Instantiate from Jobs will never be able to work. The way you would do it is use the Jobs to perform calculations and processing needed, populate an array with the results, then on the main thread loop over that array to instantiate and apply the results.

    That said, instantiating hundreds of thousands of GameObjects at one time isn't ideal, it will be slow no matter what you do. Are you instantiating every frame or is this a one-time allocation? Have you considered pooling the GO's at load time, or using a Coroutine to split the work across multiple frames?

    What ever you decide to do, the last year has not been wasted as it is a learning experience. You can take the knowledge you have gained from implementing it in single threaded MonoBehaviors and apply it to whatever direction you decide to go in. If you decide to rebuild it from the ground with ECS and Jobs in mind then you will likely be able to reuse a lot of the logic you have already written. If you decide to go down the hybrid route you should be able to reuse a lot of the MonoBehaviors you've written over the last year. It might only take you, say, a month to rebuild what took you a year the first time as you already know the solutions to problems you have run into.

    Could you explain what you're trying to achieve by instantiating so many GO's in one go? With a bit more explanation you could get suggestions on how to better approach the problem and how to make it work with Jobs or ECS rather than just answers on that one specific element.
     
  6. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    It is impossible to convert everything into jobs, things that can go into a job is actually very limited to calculation stuff. Currently the only true thread-enabled Unity's classic component is Transform, which can be now changed from a job with TransformAccess. (And recently the AnimationJobs) Many other things like playing audio, changing sprite color, playing animation, manipulating particles, the whole UI system and Sprite system, drawing stuff to the screen, or even getting a delta time you still need the main thread.

    I have written the core code for 2 years and after I learned about ECS I have successfully turn them into hybrid ECS in 1.5 months. It feels overwhelming at first what to do if not demolishing the entire project, but actually one of the goal of Unity's ECS is so that only bits of your game can be turned into new system. Here's a gist of how to think about it :

    - The biggest gain from ECS is the iteration of data. Do you see a big data (probably in class) being iterated a lot every frame or something in your game? If so the first step is to separate that data from a class and put it in struct, so they can be put in a job. In this phase identify your game's "big calculation" because that's what the job is good at.
    - After that you can already throw them into the job and get the calculated output even without touching your Mono code. You get multithreaded performance now but not fast iteration of data used in job.
    - The next step is to make these data come from ECS's tight memory layout, so when you give them to job it is extra fast and does not require copying. (ComponentDataArray and NativeArray & friends allows this) This is the injection.
    - For this you need 2 things : make these data as a separated entity so that they can be injected.
    - And then for the injection to work you need a system, so you have to slice up your Mono code into System which requires some heavy thinking about execution orders + dependencies since they will now be loosely coupled. Your biggest enemy is to figure out how to position your new system's execution order in the middle of your old codes. This : https://gametorrahod.com/unity-ecs-how-do-i-implement-ecs-on-only-one-part-of-my-game-6db8922f3c51 and this : https://gametorrahod.com/details-of...normal-scripts-with-updatebefore-1977de86e920 might help.

    But good news is you can copy paste your mono code into systems like ComponentSystem, so it is not true that your Mono code are obsolete. I saved so many debug time related to my game logic because it worked before, it must also work in a system given the correct execution order and correct understanding of ECS and that's what wasted my time (plus some stupid copy-paste miss), assuming I have all the knowledge as I am now and do it again it would be like 2 weeks instead of 1.5 months.

    I have documented my scaffolding approach of turning the code to ECS bit by bit here if you want to know : https://gametorrahod.com/strategies-to-migrate-your-data-class-95057257168b

    And also a big list of all my frustrations over those 1.5 months here which includes over 20 sub articles I noted for myself : https://gametorrahod.com/all-of-the-unitys-ecs-job-system-gotchas-so-far-6ca80d82d19f
     
  7. dadude123

    dadude123

    Joined:
    Feb 26, 2014
    Posts:
    789
    MonoBehaviours are going nowhere, but judging just by that comment, and the statement that you're aiming to convert a whole game to ECS, I can foresee a lot more wasted years for you.

    ECS is not some magical performance sauce. Does your game even have performance problems? Or do you have enough free time to convert stuff over to ECS just for the fun of it (which would be 100% legit)?
     
  8. neounix

    neounix

    Joined:
    Jan 9, 2017
    Posts:
    28
    I already use many Coroutines though out the entire project :)

    Yes, sure. As a very high level overview, you can read this Research Gate blog post about our project and then my tech notes below (which I was going to put in a slide deck but have not have time to do so yet).

    Researchers render cyberspace like a 3D video game to make identifying threats easier

    Basically, I use the idea of a distributed blackboard architecture to implement a multi-sensor data fusion (MSDF) approach to virtualize (visualize) cyberspace in near-real time and render this a a Unity 3D application. Cyber objects (which represent actual objects in cyberspace) are managed (and are constantly updated) in external databases (on a server, which generally corresponds to a sensor), which I refer to as the "object base" (OB). That OB is converted to a JSON file and each individual object in the JSON OB has about 10 attributes. This file can contain 100s of thousands of objects and many more attributes, obviously. It is also possible to combine various object bases from different sensors (this is the core idea behind MSDF blackboard architecture) to have even larger object bases.

    I read this object base into Unity {in Start() using Coroutines} as a single file (batch process the object base), which means I read in the JSON file and parse the JSON OB into an array (all in Coroutines of course). Then we loop though that array and Instantiate() a Game Object (GO), mapping the entire OB into Unity 3D World Space. As a side note, we have plans to also update Unity in near-real time by sending updated object information in JSON format from the external sensors, adding new GOs or updating existing ones in near real time, but I have had some problems getting UNET to work properly in this architecture, which is a totally different issue as being discussed here.

    After I create the 3D mapping of all the 100s of thousands of GOs in World Space; the main camera is used to "travel" or take the user to explore the entire metaverse of virtualized cyber objects, which represents real world scenarios in cyberspace.

    So, I have serious performance problems Instantiating the GOs when the OB is large using a single thread (drops from 60 FPS to 3 FPS) and I have performance problems when traveling and exploring this metaverse when the OB is large (drops down from 60 FPS to 3 FPS when the OB is large) when there are many GOs in view (rendering).

    I forgot to mention, we also Instantiate links between the GOs and these links also have attributes, and when we run this with the links between the GOs "on" the performance grinds down to 3 FPS. The Unity profiler shows the problem to be the single main thread.

    Oh, and each GO (normally Instantiated as spheres using a prefab) has a collider because we use raycasting so the user can click on any GO and view the attributes of the GO. In addition, in autopilot mode, I use raycasting as well; so the Unity profiler also shows the Physics as a bottleneck

    We have bottlenecks everywhere (LOL), so I just went to a 12-core 2013 MacPro with 64GB of memory, but the bottle neck is in the CPU, in just about any operation that involves rendering the GOs.

    Hope that makes sense LOL.

    Sorry, I did not make a slide deck to explain better.
     
  9. neounix

    neounix

    Joined:
    Jan 9, 2017
    Posts:
    28
    I tried this already, but I could not successfully Instantiate GOs in a Jobs struct using Mono.

    Also, each script in the project references these GO MonoBehaviors, so without the GOs, the entire project will have to be written to work with ECS, and this is not the first step I wanted in the port from Mono to multithread.

    My plans were, in the first step. to simply Instantiate() GOs across my 12 physical (24 virtual) cores. so see if I could significantly improve this initial setup step (described in post #8 above), but even this step is not working because it seems that the Unity Jobs system is meant to work with ECS and obsolete single threaded Mono.

    Of course, I understand the need to move to multi-threaded Unity. What I don't understand is why C# Job is not implemented in Unity (at this point in time) in a manner which works with Mono and all our existing code base; instead of (perhaps intentionally) forcing developers like me to rewrite the entire project in ECS and Jobs, which does mean all this year of hard coding effort in Mono was a waste of time. It sure seems that way when I sit down and the computer and try to covert this exiting Mono project to run multi-threaded with the 2018 Unity Jobs system.

    *sigh*
     
    RendergonPolygons likes this.
  10. neounix

    neounix

    Joined:
    Jan 9, 2017
    Posts:
    28
    Hopefully, I answered this question in post #8 above. If not, hit me up again, LOL
     
  11. starikcetin

    starikcetin

    Joined:
    Dec 7, 2017
    Posts:
    340
    You didn't waste anything. The old versions are still there, you can hold on to 2017.4 and continue using the MonoBehaviour paradigm.
     
  12. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    When I say "system" it does not mean "Job". In fact ComponentSystem cannot run a dependency enabled job. Everything from mono including instantiation will work in CS's OnUpdate and this is still a step towards ECS. That's why copy paste works and I recommend you do start from this. And no matter what you cannot instantiate real GO in a job so don't try to do it.. Just move them to CS and get the benefit of ECS that now you can inject data. Still not multithreaded, but better.

    This is not a good first step as speeding up instantiation means you have to use Entity with mono components instead of game objects you can click on the hierarchy. More work will follow from that especially if you have some rendering component on it. My advise is to keep the old GO instantiation (but move to ComponentSystem) separate the data that needs calculation out, use job + multithread to calculate as much as you want + then finally give them back to your GO. When your old logic are in CS you can also inject the GO directly in the main thread by having GameObjectEntity on your GO prefab.
     
  13. BrianWill

    BrianWill

    Joined:
    Oct 10, 2014
    Posts:
    38
    The Job system can't directly work with GO's and their components because the jobs can't touch managed objects. I guess a Job System that allowed you to touch managed objects is conceivable, but it would be greatly hampered by thread safety concerns.

    You could create your own C# threads instead of using the Job System, but I think GO's can only be safely created in the main thread. If your bottleneck is instantiating too many GO's, the limitation is probably baked into the nature of GO's, and splitting their creation across threads could even make it worse due to memory contention. How many objects are we talking about, any way?
     
  14. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    This is because multithreading a job is not trivial and needs a handcrafted solution. You cannot have just certain algorithm and suddenly turn the entire Unity codebase into a multithread enabled program. Existing multihtreaded includes TransformAccess which might be the first one Unity finished converting (from Transform). And then we have AnimationJobs now. Also many things are also already multithreaded if you look in Profiler like how Animation system works you will see worker thread has some work to do there as light green blocks. "why C# Job is not implemented in Unity (at this point in time)" the answer is simply because they are not done yet. By the time everything becomes multithreaded like you said you might have to wait until year 2022, for example. If you want that you would have to wait more.

    But Unity is giving us what's usable right now to work with. The most important being C# Jobs with the copy-to-isolated-struct approach + important key that is native containers which is able to be in a struct while linking memory to outside of the struct. That is as generic as it can get to be able to multithread as many situation as possible, while not so automatic as someone would like it to be.

    For example for a job like finding a number red balls in a pile of multiple colored balls you might be able to say divide the balls equally to several person and have them count by themselves, and finally when everyone are done sum all the answer together is the solution.

    But not all job is trivial like this and involves inter communication. Which part of the job is the critical section and requires mutual exclusion? From the example I said, if I allow anyone that had finished counting their own balls to add to the total amount immediately that total amount would be the mutex as it requires reading and writing to update it. In your game there are millions of possibility what is this mutex, and the solution to multithread work is this critical section which needs manual design and therefore it cannot be automatic. (IIRC Dijkstra states this first you might want to see the one page paper : http://www.di.ens.fr/~pouzet/cours/systeme/bib/dijkstra.pdf)
     
  15. Devilbox-Games

    Devilbox-Games

    Joined:
    Jul 3, 2012
    Posts:
    205
    It sounds like the instantiating is only a small part of the issue. From your post it seems you're currently only instantiating when the scene is loaded, so the general poor performance is unrelated to it. Ignoring the instantiation part, brute force rendering that many meshes is always going to be slow due to the amount of draw calls involved. Similarly using the built in physics system to put colliders on all the hundreds of thousands of objects to raycast against is probably not entirely sensible. Even if Unity added a feature to automatically make GO's multithreaded I honestly don't think you'd be getting the performance you want out of your project without substantially redesigning it anyway. With such a massive dataset you can't rely on the built in GO-based systems to perform well, it's just not designed to handle it out of the box.

    Moving the project to use ECS is probably the best option for performance, then you can write your own lightweight jobified raycasting system, use MeshInstanceRendererComponent to batch thousands of meshes into a single draw call and write a culling system to only work with data of immediate importance.

    You can use your existing code as a reference, a lot of the logic can no doubt be reused with minimal rejigging as 5ragon suggested. As I said before, that year developing this prototype isn't wasted as it gives you a clear spec for what you need to make using ECS. Sometimes it's far more beneficial and less time consuming to start from the ground up in a more suitable system than to try fixing an existing codebase in an unsuitable system. Really ECS's data orientated design approach sound like a much better fit for your use case than GO's object orientated design regardless of multithreading.
     
  16. neounix

    neounix

    Joined:
    Jan 9, 2017
    Posts:
    28
    Thanks for all the well thought out replies.

    The takeaway for me is to redesign the project using ECS first (with Jobs in mind); and after we are happy with single threaded ECS, then add Jobs.

    Or maybe just do each step in the new design, one step at a time, using both ECS and the Jobs system, from start to finish?
     
  17. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    This approach is not recommended because you will not be able to run the project for a very long time, and by the time you can run you probably have to fix too many bugs that may or may not be related to each other. It is better if each small step you take results in a playable game like before. Again, this article has some hints of how to do it.
     
    Lurking-Ninja and dadude123 like this.
  18. neounix

    neounix

    Joined:
    Jan 9, 2017
    Posts:
    28
    Thanks again 5argon.

    I think best I focus on other projects until ECS matures with better documentation and tutorials.

    You and others here are much better C# OO programmers than me and so I need to wait until all of this matures further before I dive into changing a fairly big project which took me a year to code!

    Update (Reference) .... See also my update at unix.com:

    Up Against a Brick Wall with Cyberspace SA and Unity3D
     
    Last edited: Jun 23, 2018
  19. ParhamXTT

    ParhamXTT

    Joined:
    May 24, 2020
    Posts:
    28
    I did your mistake from 2012 until now.
     
  20. slava_klymenko

    slava_klymenko

    Joined:
    Sep 13, 2021
    Posts:
    2
    So if I want to build a Minecraft game, where each block is loaded from a prefab - Jobs / ECS / multithreading / or any other magic stick will not help me to load/unload hundreds/thousands objects every second from/into RAM based (on distance to player) ?
     
  21. Laicasaane

    Laicasaane

    Joined:
    Apr 15, 2015
    Posts:
    361
    No. Because ECS doesn't work with "object" in the first place. ECS works with data. You are thinking in Object-oriented way so ECS won't help you with that. In ECS way, you don't think about "prefab" or "gameobject", but "a chunk of data".

    So yes, ECS will help you generate and process thousands data entries on RAM, but NOT "thousands objects".
     
    slava_klymenko likes this.