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

How do the native side and scripting runtime side of unity interact?

Discussion in 'Scripting' started by pk1234dva, Oct 12, 2021.

  1. pk1234dva

    pk1234dva

    Joined:
    May 27, 2019
    Posts:
    84
    I never really understood this fully - what exactly is happening when I do something like say GameObject.FindObjectsOfType<Collider>() ?

    Is there at all times a mirrored structure in the scripting runtime, of what exists on the native side? I.e. for each "conceptual" transform in the scene, there's a transform struct in native memory, and an instance of some tranform class in the scripting runtime? So when I do a call like the above, where does the processing take place - on the native side, or on the scripting runtime side?

    Or maybe on certain specific types, like Transforms, call concerning them even in the scripting runtime, are automatically treated as native calls, by some type of trick?

    I'd appreciate a detailed explanation of what really happens, or a reference to some blog/article that explains what happens in depth.
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,750
    There's a game engine. It's Unity. One of its functions is to mount (prepare to execute) a DLL that contains all your code.

    Once mounted, Unity loads scene 0. That's it.

    By agreement, elements in that scene specify code in your DLL is executed. Here's how and when:

    https://docs.unity3d.com/Manual/ExecutionOrder.html

    Your code can make calls into the .NET API to get at operating system stuff.

    Your code can also make calls back into the Unity engine.

    Where things are stored is not really relevant. You must use a the appropriate API to access things.

    Some of those calls give you access to the asset database shipped with the engine, which is all your assets packed up for rapid platform-specific access.
     
    Joe-Censored and hippocoder like this.
  3. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Unity calls an engine function that searches the scene graph for objects of that type in a very slow, plodding manner. It's usually best to keep references of all the objects you need to be working with instead.

    So it's fine to use it in the Start() or Awake() calls.

    You can also do what Unity does manually by getting a reference to the root of the scene and iterating recursively over everything for matching types. Unity hasn't really got many acceleration structures in place for it's Find* methods, they're going to be something you use for setup, once.

    The docs mention how slow this stuff is too. But don't hesitate to make more posts for each query you have, it is after all why the forums exist :)
     
    Joe-Censored likes this.
  4. pk1234dva

    pk1234dva

    Joined:
    May 27, 2019
    Posts:
    84
    Thanks for the replies guys.

    I was under the impression that on most platforms (with some exceptions like WebGL), Unity will host a CLR-like runtime, that will run certain byte-code compiled from your scripts, because this is necessary for allowing .NET API use. Is this not correct?

    What I'd like to understand, is how exactly this happens, and how it is determined which code effects the undelying, native structures directly, and which code goes through some type of .NET -like runtime environment.

    Maybe a more concrete example will be useful:
    Suppose I have a simple monobehaviour
    Code (csharp):
    1. private void Start()
    2. {
    3. transform.position = Vector3.zero;
    4. list = new List<int>();
    5. }
    and this monobehaviour is attached to one active object in your scene, besides that, the scene is empty.

    Here's what I would guess happens in build, once we run it -
    The native side of unity has a list of the game objects in the scene. At some point, it will iterate through them, and "call Start()" for each active one (maybe there's exceptions, that's not important in this context) - but how exactly does this "call" happen / in what form is the Start() method?

    Is Start() compiled to native code? Or is it compiled to a IL / byte code method? I'd assume it's the latter, but I'm not sure. There has to be some type of pointer/connection between the actual native object, and a structure that actually has the Start() code, and I don't get how this is done.
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,750
    Yes. Read about IL2CPP targets.

    No. Read about Mono targets.

    Is it relevant? Unity maintains a giant list of "All the stuff happening" and iterates it each frame and looks for your code to call on it. When it calls your code it gives references to that item, which you can use in gameObject, transform, etc.

    Remember, the app IS Unity, not your code. The Unity game engine happens to let you hang your code on bits of its objects, and it is the only one in charge of that bookkeeping, so it knows exactly where everything is. It's just pointers and references and lists and graphs and oh my, tedious list maintenance stuff that computers are really good at.
     
    Joe-Censored likes this.
  6. kru

    kru

    Joined:
    Jan 19, 2013
    Posts:
    452
    I think it is relevant to have some understanding of the interop between unity native and unity managed spaces. That will help improve understanding of why code like "if (this == null)" is sometimes needed.
     
    PutridEx likes this.
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,750
    That's just an operator overload that Unity provides for the UnityEngine.Object class.

    It is the subject of dozens of threads and hundreds (if not thousands) of passionately-posted missives about "what should be done" about it, such as this:

    https://forum.unity.com/threads/dep...and-fake-null-for-unityengine-object.1107368/

    It has never caused me any issues so I am unsure why everybody gets worked up about it.
     
    Joe-Censored likes this.
  8. pk1234dva

    pk1234dva

    Joined:
    May 27, 2019
    Posts:
    84
    I'm just curious and interested about how it works, that's all.

    I suppose that assuming il2cpp, it's a bit easier for me to reason about what would happen - transform.position = Vector3.zero can get compiled directly to an assignment to the underlying native structure. A list creation will simply compile to a list structure being allocated on the heap, and also adding the new allocation and the local object to some garbage collector type structure.

    I guess I'm mostly curious about what happens when using Mono and I should have specified that, because that's what I really don't get. Because when using Mono, you only really have IL / byte code... and yet this code is called as if it were a method of some object, so I would think there is some corresponding object in Mono runtime, of which the method Start() is called. But perhaps this isn't the case... where do the Monobehaviour data structures exist? Do they only exist natively and are passed into calls to the Mono runtime methods? If I have a custom monobehaviour with some public field, is the object on both the native side, and in the mono runtime side?