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

Why Not Make Unity MonoBehavior Thread Safe for C# Jobs?

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

  1. neounix

    neounix

    Joined:
    Jan 9, 2017
    Posts:
    28
    Dear Unity Developers @ Unity,

    Sorry, I'm not trying to be a PITA.

    However, it seems to (dumb me) like it would have been better for Unity and much of the developer community of Unity users if the Unity developers had of updated MonoBehavior for Unity to work in a thread safe environment with the requirement for ECS.

    Yes, a "completely new way of coding in Unity" (as you say) ECS with the Jobs system will eventually give developers great performance gains.

    BUT....

    In the meantime, why not have created a Unity development path to upgrade Unity MonoBehaviour so it would work in a thread-safe way so all existing code would easily work with Jobs?

    This seems like the right thing to do for all of the legions of Unity developers who have written zillions of lines of MonoBehavior code as a community and who want to submit Jobs to multi-core machines without a "completely new way of coding in Unity" (as you said, Unity).

    There is a very old saying, which seems to ring true here:

    "The Enemy of Good is Great".

    Updating MonoBehaviour for Unity to work with multi-core machines would have been Good.

    Creating a Great ECS/Jobs system seems to be the short term (and maybe even long term) enemy of all who have spent years and a lot of money developing in Unity who wanted a working solution in Unity for multi-core machines using MonoBehavior.

    After all, it's not the Unity customers "fault" that Unity did not design Unity3D to work on a multi-core / multi-threaded environment from the beginning (the get-go, as they say).

    Isn't is better for Unity developers to fix this problem and make all Unity C# code easily forward compatible with the Jobs system versus forcing all Unity developers to learn a "completely new way of coding in Unity" (as you said) ... ECS?

    Isn't "The Enemy of Good, Great?"

    Seems something was not well thought out, at least to me who is not very smart, LOL

    ECS seems Great; but why throw the Baby out with the Bath Water for ECS and not create a thread-safe jobs system for all existing MonoBehavior code and classes?

    ?
     
  2. rz_0lento

    rz_0lento

    Joined:
    Oct 8, 2013
    Posts:
    2,361
    You can already use the new job system without ECS.
     
  3. neounix

    neounix

    Joined:
    Jan 9, 2017
    Posts:
    28
    No @rizu. You cannot Instantiate GameObjects or perform any basic Unity MonoBehaviors related to GOs, etc with the Job System. (This has been discussed many times in many threads).

    Unity has made it clear that Unity MonoBehavior is not thread-safe.

    There are many threads and discussions about Unity not thread-safe with MonoBehaviors and you cannot even do basic tasks like Instantiate a GO with the Unity Job system. (There is at least one very good thread on this techincal fact in this forum).

    In addition, developers are also told we cannot use the C# System.Threading Namespace in Unity because Unity MonoBehavior is not thread safe.

    We have been told many times that we must use ECS because of this fundamental Unity architectural (original sin, not thread-safe) flaw. Of course, we cannot get a complex game to work with Unity MonoBehavior Namespace (the core of the entire Unity architecture) because it is not thread-safe.

    Jobs cannot work if the core architecture is not thread-safe.

    This is the problem which, in my opinion, Unity should have fixed first before launching the "mother of all future performance benefits" which is not backwards compatible with years of Unity code development.

    Sorry, I'm not trying to be a PITA,

    But when you realize that a years worth of work in Unity, writing MonoBehavior code that is based on the concept of GameObjects and references to GOs, while waiting for a Unity solution for multi-core, was basically a waste of time .... it's hard to paint a rosy picture and it's hard
    not be unhappy or disappointed in the Unity "new way of coding" which tosses most of the old way of coding (and hence hundreds of thousands of dollars worth of work) out the window. :(

     
    Last edited: Jun 28, 2018
  4. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Api thread safety, jobs, and ECS are three very different things. They don't need to nor should they all be made to progress in lock step with each other. In fact you would kind of need to get jobs fairly stable before you go making other api's thread safe, if they are going to do that via the job system. So the order of it all makes pretty good sense.

    Making the rest of the Unity api thread safe is something that will obviously take the longest amount of time here. Likely a multi year effort before it's all done.

    They are simply doing what any good platform would do. Releasing what they can as they can.

    And you are exaggerating things a bit here. Like instantiating stuff in jobs, not even on the top of my list nor anyone else I imagine that is making heavy use of jobs. And I've been writing substantial concurrent logic in the context of Unity for years. When jobs came out I converted several thousand lines of code that I was running in separate threads, most of that code in Monobehaviors. Just not calling api methods that were not thread safe.
     
    5argon likes this.
  5. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    By MonoBehaviour code thread safe I think you mean you want all of Unity Scripting API to be thread safe, and not a new kind of MonoBehaviour (let's say ParallelMonoBehaviour) which can run its own update in parallel and somehow take care of possible critical section automatically? (like changing transform at the same time, play different audio at the same time, read any data that are being written at the same time etc.)

    I think Unity API being thread safe or not is not directly related to ECS. It is related to Job system. And so it is indirectly related to only JobComponentSystem of ECS.

    Even "with" this completely new way of coding (ECS) you still cannot submit Unity API to jobs. If Unity API is parallelized both Mono and ECS would be able to do it in jobs.

    It is not really enemy but rather a different feature. Currently I might say ECS progress : 30% C# Jobs progress : 60% Unity API thread safe : 1% (TransformAccess is one of them) Even with a great ECS + Jobs it will not hamper nor progress the Unity API parallel feature. I think the question then should be "Spending development time creating a Great ECS/Jobs system seems to be the short term (and maybe even long term) enemy..." now that is a understandable request.

    It would be the best for sure if we have Unity API executable in jobs. Everyone want that. But again even using ECS is not going to make parallel Unity API possible. I think the question then should be "Isn't it better if Unity team prioritize development time on parallel Unity API development before the (unrelated) ECS and Jobs system?"

    The main highlight of ECS is to iterate on a tight data, but it comes with a bonus of working nicely with C# Jobs via JobComponentSystem where you could get data from EntityDataManger and forward it quickly to jobs. Both ECS and C# Jobs is not related to Unity API being able to parallel or not.

    The question then might become "Why not create a thread-safe jobs system for all existing MonoBehavior code and classes first before creating ECS system". Of course that thread-safe jobs system is C# Jobs (copy to struct approach) and it does not work with Unity API. (the important subset of "all existing MonoBehavior code and class", the other remaining subset being your own code which would need some data copying to be able to parallel)


    Even with ECS we still cannot avoid this flaw. With ECS you must still call Unity API on main thread (ComponentSystem, or non-job part of JobComponentSystem)

    The current Job system works (the function of it being use the worker thread to run logic with some nice dependency system), but core architecture cannot be used in it.

    If you have a parallelized core architecture, you still need a Job system in order to take advantage of it. So again these are 2 different things.

    This is a great and understandable request!


    ...so after all the clarification all the things comes down to development priority of these 3 features :
    1. ECS
    2. C# Jobs System
    3. 100% Parallelized Unity API, no error whatsoever no matter what you call on the other thread.

    And what you are expressing is that you don't like the order of development the Unity team decided at the moment : C# Jobs -> ECS -> Parallelized Unity API and would like the paralellized API to come first before any development of C# Jobs and ECS, so that it would be immediately usable in MonoBehaviour (With C# Task Parallel Library, because C# Jobs is later in the development roadmap) by creating a new thread in TPL and freely call things like rectTransform.anchoredPosition = .... without problems.

    But in my opinion, I agree with the current roadmap ordering of C# Jobs -> ECS -> Parallelized Unity API. Because converting the whole API I guess seems like it would be multiple year of work. While waiting for that I already have a blank-canvas framework like C# Jobs and ECS to play with while waiting for that. (I really love that I don't have to do TPL as it automatically use existing worker thread)

    If the actual time to parallelize Unity API is short like 3 months, then I would really be PITA why Unity team didn't put the time to put that out before C# Jobs and ECS which require a big migration and rethinking. But I don't think that short development time is the case so I am content with this roadmap.
     
    Last edited: Jun 28, 2018
  6. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    The others have given their answers, but I'll go ahead and give mine too:

    I have written a lot of C# threaded code that interacts with Unity over the last few years, and this is written from the basis of understanding that I have of Unity's internals, without seeing the source code.

    I have worked on projects that used marshalling to have C++ and C# code talk to each other for a client project some time back (not-unity). That alone is hard to get right. Screw that up and the GC explodes, and your app crashes. Or produces a security hole. Or any other horrible thing that "undefined behavior" does.

    GameObject, Component, and ScriptableObject all have a both a native object in C++ land and a Managed Object in C# land. MonoBehaviour and ScriptableObject, in particular, are effectively the same type of C++ object under the covers. Most heavy-duty Unity.Object classes likely also have a C++ counterpart, if not all of them.

    Why is this important, and why does it effectively make MonoBehaviour / normal Unity API in general not thread-safe?

    The Garbage Collector, that's why.

    The garbage collector can move managed objects around in the heap, and can freeze all non-native threads on the application to do it. NATIVE CODE CALLED WILL STILL KEEP RUNNING HOWEVER, it doesn't belong to the CLR and won't stop executing. Meaning it's very likely that an operation on a non-thread-safe Unity API could cause havoc if it called a Native function that expect to return in lock step while the GC is running.

    If a Unity native API gets called on the main thread and the GC finishes, no big deal, we'll continue managed execution right where we left off, objects stay in sync everything is fine.

    But let's look at the following scenario:

    1. We kick off a thread to create a bunch of GameObjects with MonoBehaviours.
    2. On the Main thread, we create another bunch of GameObjects with MonoBehaviours.
    3. The GC is run to get more space for this, while we're building 2 very different components, one has more native data than the other and takes longer to create.
    4. C++ side for both merrily continues on, then returns.
    5. Garbage collector rearranges a bunch of the managed objects we just made.
    6. The threads resume, with no guarantee on the order they begin executing again whatsoever.
    7. C++ handles to managed halves are no longer in sync, here there be UNDEFINED BEHAVIOR
    This goes well beyond object creation, and touches a lot of things.

    I'm not trying to be condescending, but your question is like asking: Why are the standard .NET containers not thread safe, and why did they make new thread-safe containers instead of making the standard ones safe? (The only one remotely thread safe is a
    Queue<T>
    with a
    lock
    on it, and that's it's own can of worms).

    The answer is presumably because it would introduce more maintenance and execution complexity to a system that is already over-complicated as it is. Better to build brand new leaner thread-safe functionality using the lessons learned than try to retrofit an API designed around the constraints of being single-threaded (and there's likely performance implications on top of this). If it was easy enough to do without breaking every ongoing Unity project on the planet completely, they likely would have done it already or announced it as soon as the .NET upgrade had finished.

    Writing a single-threaded API and a API intended for multi-threading are two different problems. Does it suck? Kinda. Can you work around it? Yeah, it's quite possible to work around it, a lot of us already have.

    I do recommend UniRx for dealing with swapping back and forth between Unity API and .NET threads. If you move a lot of your data into plan CLR objects, you can at least build MonoBehaviour data or configuration data off the main thread and buffer GO+MonoBehaviours to be created via factory patterns.
     
    Fajlworks and dadude123 like this.
  7. avvie

    avvie

    Joined:
    Jan 26, 2014
    Posts:
    74
    To summarize, there exist multithreaded capabilities and tools in .net at your fingertips already. but anyone that had to do multithreaded code can tell you that unless you are theoretical genius its a lot of pain.
    Locks will help to get the job done, but begs the question why are they not used? The answer most times is that the overhead and the rest of the worms in the can that was mentioned by recursive make it not worth, sometimes it actually makes it better to do it singlethreaded. The promise from unity is exactly what is needed to make multithreading approachable, easy, modular and consistent.
     
  8. neounix

    neounix

    Joined:
    Jan 9, 2017
    Posts:
    28
    @5argon, Yes. You said it much better than I could. Thanks.

    Agreed. The issue is the Unity API is not thread safe and therefore everything follows. Thanks again for restating in a better way.

    Yes, that is what I think should have been the first priority for Unity developers when considering how to modernize the Unity engine.

    Yes, @5argon that's exactly the question I want to ask. Thank you again for clarifying what I'm trying to express here (out of frustration on my side).

    Yes, That is exactly what I think was the mistake (in development priority) Unity made. IMO, Unity developers should have started creating a thread-safe system (years ago) for all existing MonoBehavior code and classes and build ECS on top of that.



    Yes.

    Here is where our opinions diverge, @5argon . But that's OK.

    IMO, It would have been much better for everyone and especially the legions of Unity developers who have written zillions of lines of C# Unity MonoBehaviour, for Unity to have invested the time and resources to create a game engine which works in a fully parallel architecture; and then build a ECS system on top of that.

    For example, a game developer should be able to Instantiate 100s of thousands (maybe 100s of millions in the future) of MonoBehaviour GameObjects including Transforms and Physics for each GO in parallel, perfectly thread-safe and deterministic. With a solid underlying foundation of parallelism, developers could create games and simulations that are impossible (from a performance perspective) in a single main game engine thread as it is now. This "new parallelism" opens up game development to an exciting future decoupled from the constraints of the current Unity "single main thread, single CPU core" , not parallel" architecture.

    And, of course, if done correctly, the goal is that only minor changes to all the zillions of lines of code written in C# by Unity developers worldwide would be required to advance the state-of-the-art. Fully backwards compatible parallelism.

    After all.... (some philosophy and music please)

    Life, is not single-threaded. We are not single-threaded creatures. The Universe is not single-threaded. Physics in the real-world is not single-threaded. Parallelism should be the foundation, not a feature of modern game engines.

    It is really mystifying why Unity developers have not been working on this for the last decade and therefore a 100% Parallelized Unity API should have already been here years ago (or at least close being 100% done).

    For me, and maybe I'm just being selfish and a PITA, but I am only interested in building applications where 100s of thousand of GOs are Instantiated in parallel in a game engine where even when the GOs are Instantiated in parallel, the Physics between all GOs are fully deterministic. In this way, I can continue my research into cybersecurity and my stated research goals to create metaverses which simulates real-world cyber-objects. This area of research has application to many fields and domains, and of course, the core idea also makes the future of gaming in general more exciting.

    When I embarked on this journey with Unity, from a personal perspective, I had no idea that the underlying Unity engine was not thread-safe and therefore I was doomed to be trapped in a single threaded world, when want I thought (and this assumption was wrong on my part, my bad) was the the Unity game engine could create GOs (and all the needed Physics) in a fully multi-threaded, parallel architecture, and scale with parallel CPU cores.

    Anyway, thanks for all the great and thoughtful replies and sorry that I'm a disheartened PITA about this.
     
  9. LazyGameDevZA

    LazyGameDevZA

    Joined:
    Nov 10, 2016
    Posts:
    143
    @neounix It feels like you've missed a fundamental point in why Unity is diverging from trying to make MonoBehaviour thread-safe. The way the C# ecosystem manages objects and allocates memory is the big crutch here. I've read some posts about having a better Garbage Collector would help, I'm not however convinced that it would have proven to be a big gain in terms of performance compared to what Unity is pushing for now. The main issue is in how memory is laid out with managed objects. You aren't guaranteed that related data would be put together, thus resulting in cache misses and leaving the CPU having to sit idle while the correct batch of memory is loaded for the next operation. Had MonoBehavours been thread I don't see how this would've effected the performance bottleneck that this holds.

    Going the naive way of "making MonoBehaviour thread-safe" would include a lot of locks which would likely end up with contention between threads trying to read/write from these objects. This can sometimes end up being worse than running everything single-threaded because with locks around certain parts you're likely going to still have threads sit and wait for other threads to complete operation on data until the other thread is done operating.

    My key takeaway is that multi-threaded programming is extremely difficult and there's a reason why I'm excited that Unity is providing a simple API, albeit with some restrictions on the datatypes that I can use, with guarantees that I won't end up with race-conditions on my hands. I'm have little experience of multi-threaded processing at the level one would see in a game engine like this, my day job as a Software Engineer still leaves me with small subsets of data that are processed and aren't as dynamic as game data so most of the use-cases are quite clear which allows us to use containers that fit the problem. Even then the data was still living in managed types which would cause cache misses so I haven't even had real experience en getting a multi-threaded solution that performs in an optimal way.

    The point I'm trying to make is that understanding why the decision was made is important in this case rather than suggesting they aren't taking this decision seriously.
     
    Ammarz, amarcolina and 5argon like this.
  10. neounix

    neounix

    Joined:
    Jan 9, 2017
    Posts:
    28
    .
    It's well documented, and has been so for well over a decade, that making game engines thread-safe is difficult

    Just a quick Google search for multithreaded game engine yields nearly 800K results and a simple search for thread-safe game engines yields millions of results going back well over a decade.

    The important of multi-threading and parallelism for game engines is well documented in many years of scientific and technical research papers.

    Of course, there are game developers who do not need multi-core and parallelism. However, there are many who do want to develop applications where parallelism is critically important.

    I have not read anything that leads me to believe that Unity has not made a mistake in not prioritizing creating a game engine which is truly-thread safe which works well with all existing code; versus the current development path which Unity has stated is a radical departure from MonoBehavior and is not backward compatible with the zillions of lines of MonoBehavior code already written.

    I think, and have not read any post or argument to change the view (yet I remain open minded), that Unity developers should have given the first priority to making the Unity API thread-safe and backwards compatible with the large ecosystem of MonoBehavior code and assets versus releasing an ECS and Jobs system which is not compatible with MonoBehavior.

    It's better, for the long term future of gaming engines, to build a strong underlying thread-safe game engine architecture (which is well documented to be difficult) than bolting on pseudo-thread-safe code which is incompatible with MonoBehavior (going the easy route).

    On the other hand, I realize that true thread-safe parallelism in gaming engines is a (well documented) difficult problem and I understand why Unity developers may not want to work on making the existing Unity MonoBehaviors threads-safe.

    It is less difficult to go the ECS / Jobs route for Unity developers; but the cost is that years of time and zillions of lines of MonoBehavior C# code written by Unity customers will be eventually be obsolete. Personally, I was hoping for a solution which works well with the existing Unity architecture and underlying Unity API.

    That's life I guess, and I am admitted unhappy (and therefore a royal PITA about this) that I developed an application for over a year which was waiting for a true parallel Unity API which works with MonoBehaviors, but instead, wasted a year of my life sitting on my duff coding, only to get a most incompatible ECS and Jobs "bolt on" system for multi-threading which does not address the underlying lack of a thread-safe Unity API, by all accounts.

    Peace.
     
    Last edited: Jun 30, 2018
  11. Cheerio

    Cheerio

    Joined:
    Aug 3, 2013
    Posts:
    19
    To share a different perspective, I’ve been working on a game for almost five years and has been live for almost two years and we used to be running on Unity 5.6.3p4 but we recently upgraded to 2018 because we want to move our code over to ecs. If Unity had made their API thread safe, we probably wouldn’t have upgraded because it wouldn’t have been more performant than the systems we built. The main problem is that because MonoBehaviours are classes, they can never come close to the performance of the new ecs system. The new ecs gets rid of the need for object pooling, makes instantiation/deletion much quicker and makes your update calls much faster even without threading. This makes it so any new code we write runs pretty fast by default without the programmer/designer having to keep a lot of performance traps in mind and if we need more performance, it’s much easier to turn that code into highly performant jobs than it could ever be with MonoBehaviours. For us, that’s why ECS makes a lot more sense than if Unity worked on making a thread safe api.
     
    SugoiDev, Ammarz and optimise like this.