3
struct Tile
{
    GameObject go;
    ... //more fields

    public Tile(GameObject g, Object s)
    {
        go = g;
        ...
    }
}
Tile[,] tilemap = new Tile[1000,1000]

Tile struct consists of a GameObject (a Unity Engine in-game object that's already in the scene/memory) and other fields. It can take up huge amount of memory considering that I want to store a million of them.

I don't have much experience with pointers in C#.

Would it be wise to just store the pointer to the GameObject in the struct?

struct Tile
{
    GameObject* go;
    ...
}
  • If you are designing large classes, use `class` instead of `struct`, and create objects of the class using `new`, so that the objects are created on the large heap instead of the small stack. – Tobias Knauss Apr 29 '17 at 19:24

3 Answers3

3

A reference is already a pointer (under the hood) in C#. It is NOT a copy of some object. In your first code snippet "go" is actually a pointer to an actual object. You're worrying about something that isn't true.

One of the differences between a ref (e.g. "go") and an unsafe pointer is that a ref can move, the GC can and will (when it feels like it) move objects around (and automatically change refs).

So there's no problem here, just a perception that is incorrect.

Hugh
  • 748
  • 9
  • 9
  • Not quite nothing, perhaps. @Hugh is right that the reference is a pointer, and the answer given by @Graeme is incorrect to suggest that copying the struct would result in a new copy of the `GameObject`. However I would question the decision to use a `struct` at all instead of declaring `Tile` as a `class`. As Eric Lippert points out in http://stackoverflow.com/a/9315211/1134217, genuine use cases for structs are few and far between, and so there could be a performance hit just for that. – Daniel Hume Apr 29 '17 at 15:56
  • I have updated my answer for clarity as pointed out by @DanielHume. I was not implying that a copy of the GameObject would be made, but a copy of the struct element, just not very well phrased. – Graeme Apr 29 '17 at 16:34
2

You can't use a class as a pointer. Maybe this has changed in the latest C# but I am not sure. That simply will not work. Any reference type will not work with pointers.

Simple type such as int,float and bool and others can be used as pointers.

Even if you declare the GameObject go variable in the Tile struct, it is not fine.

It is not recommended to declare a reference type inside a struct especially in a gaming app because that will make the garbage collector unnecessarily scan for objects to destroy in the struct which slows stuff down. This is even worse when you have millions of those structs with reference objects inside them.


My suggestion is to use integer instead of GameObject *. That integer should be initialized with the GameObject's instanceID instead of the the GameObject reference or pointer.

You can then use Dictionary to map the instanceID(int) to the GameObject. When you want to access the GameObject, provide the intanceID stored in the Tile struct. This will speed up everything.

Example:

struct Tile
{
    int goID;

    public Tile(int goID)
    {
        this.goID = goID;
    }
}

Usage:

Dictionary<int, GameObject> idToObj = new Dictionary<int, GameObject>();

Tile[] tiles = new Tile[1000];
GameObject obj = new GameObject("obj");

//Create new Instance with Instance ID
tiles[0] = new Tile(obj.GetInstanceID());

//Add to Dictionary
idToObj.Add(obj.GetInstanceID(), obj);

Finally, it worth testing this to see which one is faster yourself. Test with GameObject declared in the struct then with the method above that uses int and Dictionary to work.

Programmer
  • 121,791
  • 22
  • 236
  • 328
  • Use dictionary to improve memory usage? Huh? `Int32` takes same or just half of the space of reference... So using Int as key to dictionary in ideal case (ignoring dictionary overhead, just key+value) will take twice as much space as just reference... (thanks for comment how to enable `unsafe`) – Alexei Levenkov Apr 29 '17 at 15:30
  • @AlexeiLevenkov I am aware there is a dictionary overhead. You only have have to store the objects to the Dictionary during initialization **once**. Just once when the game begins. That overhead is gone. What you have now is a Dictionary that takes more space but is faster and better than store storing millions of class refs in a struct. When GC runs it has to scan through millions of the struct to find the class refs in them. Maybe I am wrong.... From the `Tile` struct class, you can access the GameObject with the goID as the key. – Programmer Apr 29 '17 at 15:36
  • Would it not depend on where the reference count is stored? I'd have thought this would be on the actual referenced object and updated whenever a reference is created or destroyed, rather than walking all exitsing vars to see if any of them reference anything? – Graeme Apr 29 '17 at 15:45
  • @Graeme I would be good to use @ as I don't know who are replying to. – Programmer Apr 29 '17 at 15:48
  • Sorry @Programmer my reply was directed towards you. I am not 100% on the internals, so it is more an assumption/question on my part. – Graeme Apr 29 '17 at 15:55
  • @Graeme It's ok. I don't really know about your question but Alexei works for MS and will likely know the answer to that question. I wish I could find the experiment that explains why a class should be declared in a struct. It was really a good article but I lost it. I believe each time GC happens, it scans all the structs with class inside it to check if the objects need to be freed. This doesn't happen when there is no reference type in the struct. That's all I know about the class in struct thing. – Programmer Apr 29 '17 at 16:31
  • 1
    @Graeme none of .Net implementation I know of use traditional reference counting for GC (in sense of https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)#Strategies). They use some form of "mark and sweep" - https://en.wikipedia.org/wiki/Tracing_garbage_collection#Na.C3.AFve_mark-and-sweep (MSDN - https://msdn.microsoft.com/en-us/library/ee787088.aspx, Unity3d - https://docs.unity3d.com/Manual/UnderstandingAutomaticMemoryManagement.html). So no, there is no space allocated for ref-counting at all. – Alexei Levenkov Apr 29 '17 at 17:58
  • @Programmer I see what you mean - indeed removing all references from `struct` would make GC scan faster for that array. I'd recommend inlining that comment about struct and GC into the post (as other may read it as "mem usage" as I did). Also this change would need to be carefully measured - you are trading potential GC efficiency vs. guaranteed increased lookup cost on every frame (`dictionary[id]` is not free). My *guess* results will be heavily dependent on how many objects are there out of 10^6 tiles - more tiles have ref to object less likely this optimization will be useful. – Alexei Levenkov Apr 29 '17 at 18:05
  • @AlexeiLevenkov Why do you say that the GC would scan the array? I was under the impression that, when structs only have value fields, the GC is never invoked on them. The struct array Tile[] written by Programmer has only an int, so it should never be checked by the GC. Are you referring to something else (i.e. not the Tile[]) or am I missing something? – Galandil Apr 29 '17 at 19:06
  • @Galandil I should have said instead of "scan" that GC will look at array, find that elements are structs without references and stop recursion for it without looking at each element. I though it was already covered in Programmer's comment but I think I read more than was written there... – Alexei Levenkov Apr 29 '17 at 20:10
  • 1
    @AlexeiLevenkov Modified my answer to add that and even ask OP to test it to see which one is faster. You can edit the answer if there is a problem. – Programmer Apr 29 '17 at 21:15
  • @AlexeiLevenkov Thanks for the clarification and links, useful stuff. – Graeme Apr 30 '17 at 12:40
1

As I understand it, GameObject is already a reference type, so it is a reference to the object not the actual object itself.

So simply storing

struct Tile {
  GameObject go;
  ...
}

would be sufficient.

If you are passing your structure arround however; the structure will be passed by value, so a copy of the entire structure will be passed to any methods unless you specifically specify to pass by reference (with the 'Ref' keyword).

Graeme
  • 1,643
  • 15
  • 27
  • I'm pretty sure that copying the struct will still only copy the reference, not the contained object. – Daniel Hume Apr 29 '17 at 16:00
  • @DanielHume indeed, but as all you ever store is the Reference all you need to copy is the reference, you wouldn't want to copy the whole object as that could be very large and costly. GameObject is a class so the variables defined as it's type will all be references not the actual objects. – Graeme Apr 29 '17 at 16:06
  • @DanielHume if you are refering to a reference to the struct itself as the reference then no, structs are litteral types and as such are passed by value unless Ref is used. Test it out, create a struct with an int, print it's value, pass it to a method and increment the value, then print the value again, you will see it is not incremented in the original struct, only the copy within the method – Graeme Apr 29 '17 at 16:09
  • so we're in agreement. You might want to reword your last para for clarity, the phrase "however, it will be passed by value" seems to suggest the *GameObject* will be passed by value along with all the other struct fields. – Daniel Hume Apr 29 '17 at 16:16
  • @DanielHume AHA! I have reworded, one of those "It made perfect sense in my head" moments :) good spot. – Graeme Apr 29 '17 at 16:22