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.

Resolved Get All Variables of Type at Runtime

Discussion in 'Scripting' started by LordOfLard, Nov 27, 2022.

  1. LordOfLard

    LordOfLard

    Joined:
    Oct 6, 2018
    Posts:
    6
    Howdy!

    Currently I've been doing some translation of code from an old engine known as Blitz3D to Unity C# and I came across an issue regarding for loops

    In Blitz3D the for loop is
    Code (BlitzBasic):
    1. For r.Rooms = Each Rooms
    2.     PreventRoomOverlap(r)
    3. Next
    and translated 90% to c# is

    Code (CSharp):
    1. foreach(Rooms r in /*Rooms*/)
    2. {
    3.     PreventRoomOverlap(r);
    4. }
    now I commented out the variable Rooms which is a class containing a few things like room position/type/etc. and what the for loop is trying to do is get every variable of the type Rooms in memory and run it through the for loop. Now I don't know how to get all variables of type at runtime (including temporary variables that haven't been disposed) I did some digging and found that maybe I'd have to access the stack somehow and get the variables from there but I'm now pretty lost and looking for pointers/solutions on how to access such data

    TLDR: I need a way to access all variables of a class type during runtime so that I can run them through a foreach statement

    EDIT: Rooms is a single class variable in the C# translation and in BlitzBasic its a type, its not an array nor a list like the grammar might suggest. I'm also translating this code from SCP: Containment Breech in case anyone is curious on why I'm even coding such weird bs. Also iI'm not gonna include the BlitzBasic version of Rooms just cause I think yall get the idea. Thank you again for the help!

    Code (CSharp):
    1. public class Rooms
    2.     {
    3.         public int zone;
    4.  
    5.         public int found;
    6.  
    7.         public int obj;
    8.         public float x, y, z;
    9.         public int angle;
    10.         public RoomTemplates RoomTemplate;
    11.  
    12.         public float dist;
    13.  
    14.         public int SoundCHN;
    15.  
    16.         //public DrawPortal dp; Forest fr;
    17.  
    18.         public int[] SoundEmitter = new int[MaxRoomEmitters];
    19.         public int[] SoundEmitterObj = new int[MaxRoomEmitters];
    20.         public int[] SoundEmitterRange = new int[MaxRoomEmitters];
    21.         public int[] SoundEmitterCHN = new int[MaxRoomEmitters];
    22.  
    23.         public int[] Lights = new int[MaxRoomLights];
    24.         public float[] LightIntensity = new float[MaxRoomLights];
    25.  
    26.         public int[] LightSprites = new int[MaxRoomLights];
    27.  
    28.         public int[] Object = new int[MaxRoomObjects];
    29.         public int[] Levers = new int[11];
    30.         //public Doors[] RoomDoors = new Doors[7];
    31.         //public NPCs[] NPC = new NPCs[12];
    32.         public Grids grid;
    33.  
    34.         public Rooms[] Adjacent = new Rooms[4];
    35.         //public Doors[] AdjDoor = new Doors[4];
    36.  
    37.         public int[] NonFreeAble = new int[10];
    38.         public int[] Textures = new int[10];
    39.  
    40.         public int MaxLights = 0;
    41.         public int[] LightSpriteHidden = new int[MaxRoomLights];
    42.         public int[] LightSpritesPivot = new int[MaxRoomLights];
    43.         public int[] LightSprites2 = new int[MaxRoomLights];
    44.         public int[] LightHidden = new int[MaxRoomLights];
    45.         public int[] LightFlicker = new int[MaxRoomLights];
    46.         public int[] AlarmRotor = new int[1];
    47.         public int[] AlarmRotorLight = new int[1];
    48.         public int TriggerboxAmount;
    49.         public int[] Triggerbox = new int[128];
    50.         public string[] TriggerboxName = new string[128];
    51.         public float MaxWayPointY;
    52.         public float[] LightR = new float[MaxRoomLights], LightG = new float[MaxRoomLights], LightB = new float[MaxRoomLights];
    53.         public int[] LightCone = new int[128];
    54.         public int[] LightConeSpark = new int[128];
    55.         public float[] LightConeSparkTimer = new float[MaxRoomLights];
    56.  
    57.         public float MinX, MinY, MinZ;
    58.         public float MaxX, MaxY, MaxZ;
    59.     }

    Conclusion: I found that this post helped the best for what I'm trying to do, any future onlookers can take a look. Since Rooms is a single class (keep in mind Rooms is not a list just a bad name) you can call Rooms() inside of class Rooms to run code to add Rooms to a static List<Rooms> Room that can then be called in the for loop. Keep in mind jvo3dc fixed the naming issue so Rooms will be Room and List<Rooms> Room will be List<Room> rooms.

     
    Last edited: Dec 2, 2022
  2. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    8,909
    I really don't follow you well.

    r is the room so it should be clear you use "r.someThing" or "SomeFunction(r)".
     
  3. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    I really don't think you want to or need to do that.

    In your example you'd have a class Room. (Really don't name it Rooms if the class represents a single room.)

    You can make a set of Room instances like this:
    Code (csharp):
    1. List<Room> rooms = new List<Room>();
    2. rooms.Add(room1);
    And then you can do a for each loop through it like this:
    Code (csharp):
    1. foreach (Room room in rooms)
    2. {
    3.    PreventRoomOverlap(room);
    4. }
    Instead of scanning the entire memory for a class type, it might make more sense to just keep track of the instances you have. (Actually, if nothing keeps track of it, it's eligible for removal by the garbage collector, that's how managed languages work.)

    It might be good to have a better look at OO principles (in C#). It can be so much more elegant (meaning more easy to maintain) than BlitzBasic.
     
    Bunny83 likes this.
  4. LordOfLard

    LordOfLard

    Joined:
    Oct 6, 2018
    Posts:
    6
    Apologies for the convoluted explanation, the topic is quite confusing it took me a few hours to even see what the code was trying to do. Basically I'm translating code from a very, very, old game and some of the coding concepts are different from todays standards, the part that I was having issues with was getting a certain class/struct from memory from all instances it was declared. Thank you for the response, I honestly think that in terms of practicality the original approach (code from BlitzBasic) is anything but reasonable.
     
  5. LordOfLard

    LordOfLard

    Joined:
    Oct 6, 2018
    Posts:
    6

    I was avoiding this only because there are a lot of temporary instances I'd have to manually track to do this but you might be right in terms of what is possible. Yes it is a bit rough searching the whole stack but this code is ran only once at runtime (with only two other loops like this) so if it is possible to search the stack for all instances of a variable it would be nice but the way you presented might also be the only possible option.

    Also in terms of the plural Rooms I forgot to just remove the 's' for clarity, it is a singular variable and I'm still scratching my head as to why its even plural in the first place, weirdest thing is in the original code there are arrays of the Rooms variable that is called room (so the array is not plural while the class/struct is). Confusing ik but imma try and put an edit to try and clear up any confusion. Thank you again for the response I'm a bit hesitant to take that approach since there's around a few hundred references to the variable in the code but it might just be between that and scrapping the project :/
     
  6. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,278
    When translating the code, it doesn't look like you are taking advantage of the Unity ecosystem. If Rooms was a MonoBehaviour, you could query all instances by calling
    FindObjectsOfType<Rooms>()
    (not that I recommend that approach generally).

    I don't know Blitz3D, but I'm guessing by your code snippet that it abstracts entity management away and bakes it into the language. In C#, you don't have anything like that because it's a general purpose language without a use-case specific runtime. Your approach of trying to inspect the stack is moving in the wrong direction. You need to keep track of your Rooms yourself. You need to create `new Rooms()` at some point, yes? When you do that, add the created room to a list. Then that list is the thing you iterate over.

    EDIT: I didn't read your responses closely. You won't be able to convert this game 1:1 for the reasons mentioned earlier. Unfortunately you will need to change the code architecture to support C# (and Unity) paradigms, there's no way around that.
     
    spiney199 likes this.
  7. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    You could make a Rooms class that acts like a factory and keeps track of the Room instances. You could even use that to reduce the new calls and recycle the Room instances that are no longer used.
    Code (csharp):
    1.  
    2. public class Rooms : List<Room>
    3. {
    4.    private Queue<Room> recycler = new Queue<Room>();
    5.  
    6.    public Room AddRoom(/* params */)
    7.    {
    8.       Room room = recycler.Count > 0 ? recycler.Dequeue() : new Room();
    9.       room.Set(/* params */);
    10.       Add(room);
    11.       return room;
    12.    }
    13.  
    14.    public void RemoveRoom(Room room)
    15.    {
    16.       Remove(room);
    17.       recycler.Enqueue(room);
    18.    }
    19.  
    20.   public void PreventRoomOverlap()
    21.   {
    22.     foreach (Room room in this)
    23.     {
    24.       PreventRoomOverlap(room);
    25.     }
    26.   }
    27. }
    28.  
     
  8. LordOfLard

    LordOfLard

    Joined:
    Oct 6, 2018
    Posts:
    6
    Oh man thank you for this response, I was worried I couldn't just translate it 1:1 but it appears the truth is pretty clear now, and you are right I will look back and add any new instance to a list and remove any myself. I am curious on if FindObjectsOfType will work, if it does I'll likely use that especially since it'll only be called a few times on the first async frame of level generation but I'm not to familiar with it (for the same reason why I should avoid it.) Thank you again for the response and thank you for giving me an alternative to look into! I'll see if I can get this thread closed today or tomorrow cause now I got a lot of options to work with.
     
  9. LordOfLard

    LordOfLard

    Joined:
    Oct 6, 2018
    Posts:
    6
    This answer is actually quite intriguing, also I didn't know that a class could be used like a variable for example a List<>. But it does also make me wonder: Is there an override of some sort that I can use for a class like OnCreate (this is an example name) so whenever the class is created like in a case like
    Code (CSharp):
    1. Rooms room1 = new Rooms();
    then it would call a void in the class that I can then add to a public static List of rooms?
     
  10. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    The class as parameter is called generics and can be applied to classes, but also to methods. Since I'm personally in the camp that prefers typing, I really enjoy the options generics add.

    On your question on whether you could add to a static List during construction. Of course. (Well, lets not take for granted that you can reference this in the constructor, since technically the object isn't constructed yet at that point.)

    Basically the same functionality as before, but in a different design. Note that this example does not have the option to remove rooms:
    Code (csharp):
    1.  
    2. public class Room
    3. {
    4.    private static List<Room> rooms = new List<Room>();
    5.  
    6.    public Room()
    7.    {
    8.       rooms.Add(this);
    9.    }
    10.  
    11.    public void PreventOverlap()
    12.    {
    13.       // The OO way, removes the need to put "Room" in the method name
    14.    }
    15.  
    16.    public static void PreventOverlapAll()
    17.    {
    18.       foreach (Room room in rooms)
    19.       {
    20.          room.PreventOverlap();
    21.       }
    22.    }
    23. }
    24.  
    As you can see I did keep that static List private, no need to expose it. So now you can call:
    Code (csharp):
    1.  
    2. room.PreventOverlap(); // Single room
    3. Room.PreventOverlapAll(); // All rooms
    4.  
     
    Last edited: Dec 1, 2022
    Yoreki likes this.
  11. LordOfLard

    LordOfLard

    Joined:
    Oct 6, 2018
    Posts:
    6
    Thanks friend! It really helps and thanks for noting that its technically not constructed yet. I woulda been bashing my head if there were errors from that. I think this is probably the best solution for me. I looked back at the Blitz3D example and I don't think the rooms ever get unreferenced. I really apricate all the help I got and really apricate yall for taking time out of your day to help me with this! It helps a lot
     
  12. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,278
    At the time the constructor is called, the object has been constructed and does exist, and all field initializers have already been applied. The constructor method allows for mutations of the created object immediately upon creation. All that is to say that
    this
    exists and is valid in class constructors and can be referenced.