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

Question Using Fixed to access members of system structs possible?

Discussion in 'Scripting' started by Unifikation, Jun 19, 2023.

  1. Unifikation

    Unifikation

    Joined:
    Jan 4, 2023
    Posts:
    1,026
    Many Unity engine features require creating a new struct to set or change a single value in that struct.

    Is it possible to use the unsafe language feature "fixed" to hold/get a reference to these structs, and simply change one value within them via pointer access?

    Or are they buried deep within the engine's C++ side and only reachable as value types copied to us mere users?
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,378
    fixed prevents the garbage collector from moving a "moveable variable":
    https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/fixed

    This means the thing in question must be moveable in the first place. A struct on the stack isn't a moveable variable since it's a value type on the stack. The garbage collector doesn't really care about that memory space. This isn't to say a struct can't end up on the heap in some way... but that's usually because it's boxed or is a member of an object.

    Note how in the examples in that link above most fixed examples are for arrays. This allows you to pin an array of some value type (say a byte array, or a string) so you can then use pointers to that array. Otherwise if you just willy nilly created a point to said array without fixed, the GC might end up moving the array on you if GC happens to occur while you're manipulating it.

    This is especially useful if you need to send said fixed array off via something like COM or p/invoke for manipulation.

    ...

    In regards to the "unity engine features creating a new struct" I believe you're likely referring to how 'Transform.position' returns a copy of the Vector3 that is its position. So you end up having to write:
    Code (csharp):
    1. var v = transform.position;
    2. v.x += 3f;
    3. transform.position = v;
    This is resulting from the fact that 'position' is a property, and therefore acts more like a method returning a result, rather than a field. C# has a way of dealing with this in regards to properties in newer versions of C# and you can do it with the 'ref' keyword:
    Code (csharp):
    1. private Vector3 m_pos;
    2. public ref Vector3 Pos => ref m_pos;
    Now you could do something like:
    Code (csharp):
    1. obj.Pos.x += 1f;
    And it'd actually directly manipulate the field.

    BUT... Unity can't easily add this with many of its properties since it's not actually accessing some private field, it's reaching into the C++ side of things. Like so:
    Code (csharp):
    1.         public Vector3 position
    2.         {
    3.             get
    4.             {
    5.                 get_position_Injected(out var ret);
    6.                 return ret;
    7.             }
    8.             set
    9.             {
    10.                 set_position_Injected(ref value);
    11.             }
    12.         }
    13.  
    14.         [MethodImpl(MethodImplOptions.InternalCall)]
    15.         [SpecialName]
    16.         private extern void get_position_Injected(out Vector3 ret);
    17.  
    18.         [MethodImpl(MethodImplOptions.InternalCall)]
    19.         [SpecialName]
    20.         private extern void set_position_Injected(ref Vector3 value);
    And other properties have nothing to do with actual stored fields at all C# or C++:
    Code (csharp):
    1.         public Vector3 localEulerAngles
    2.         {
    3.             get
    4.             {
    5.                 return localRotation.eulerAngles;
    6.             }
    7.             set
    8.             {
    9.                 localRotation = Quaternion.Euler(value);
    10.             }
    11.         }
    This isn't to say it's impossible. I mean... theoretically if Unity stores Vector3 in the same struct layout on the C++ side (which is likely), and Unity offered up a pointer address to that Vector3 on the C++ side. You could technically access it directly.

    But this would certainly have nothing todo with 'fixed', since said data is on the C++ side. The garbage collector goes no where near it. So flagging it as immovable by the garbage collector is... redundant at best?

    And as for actually doing it... honestly I don't know what it'd end up looking like on the C# side. But I wouldn't be surprised if it required the use of pointers just to access it, which would mean all of it would have to be accessed by 'unsafe' code, and thusly is problematic in as a general API (unity specifically allows enabling/disabling unsafe code)... all to what? Save some light copying?

    And if it's because you're doing this at scale and that light copying is piling up... well... unity has uncovered that in the DOTS/ECS way of doing stuff.
     
    Last edited: Jun 19, 2023
    CodeSmile and Unifikation like this.
  3. Unifikation

    Unifikation

    Joined:
    Jan 4, 2023
    Posts:
    1,026
    Wonderful writing. Thank you!!!

    An example of one area I was thinking to try this: the Shuriken Particle System. There's a bunch of modules that must be referenced, and then their "structs" gotten, edited, and a new one "returned" to set the new "value" and there's a minMax thing that's got arrays in it, which has, in one example, become a huge pain in the arse for me in newer Unity.

    Its never sat right with me that we don't have direct access to the values of Shuriken and have to do this "get, make new, set" cycle for so many aspects of dynamically editing something that should be an ECS behind the scenes.

    ColorOverLifetime is one of the simplest modules, but uses a rather large MinMaxGradient struct, and I use gradients in this, quite extensively, and swap them out at speed. There's always been a little hitch when I swap them, which leads to a little visual glitch, but it was (in 2019.4LTS) small enough that I was willing to overlook it.

    However, in everything after 2020, this hitch is now 4x bigger, and too visible, releasing the player's suspension of belief, so one way I thought might be better would be direct editing of the values in the gradient rather than swapping one whole gradient for another.

    Then I got to thinking how some of the more complex Shuriken Modules, that I use a lot, too, might benefit from direct memory access for rapid changes...

    Sadly, from what you're saying, and the failures of my little experiments at dereferencing and changing values directly in unsafe, I don't think it can work with Shuriken, for exactly the reasons you're saying - there's magic going on within the C++ side of things.