Constructors and using System.Objects

I’m writing a GUIControl base class for UI elements so that I can edit them in real-time, and hopefully persist prototypes as assets. That’s the idea - so I figured deriving from System.Object would make sense.

Unfortunately, I’m running into some extremely weird behaviour. I know that the constructor can be called at anytime, so I made an Init function that I call explicitly after constructing an empty version.

That’s works fine until I save a script, and then it clears my list fields (children and initChildren are both null) but does not clear my string field (initState is “initialized”). I can see the constructor being called, then the references are gone, and the string field seems untouched.

class GUIControl extends Object
{
    var name : System.String;
    var parent : GUIControl;
    var children : Object[];
    var position : Vector2;
    var size : Vector2;
    var visible : boolean = true;
    
    var initState : System.String = "empty";
    var initChildren : Array;
    
    function GUIControl()
    {
        Debug.Log( "GUIControl.GUIControl: initState='" + initState + "'" );
    }
        
    function Init()
    {
        Debug.Log( "GUIControl.Init" );
        // intialize to empty list
        children = new System.Object[ 0 ];
        initChildren = new Array();
        
        initState = "initialized";
    }
    
    function AddChild( control : GUIControl )
    {
        // add child to array
        children = MiscUtils.BuiltinArrayAdd( children, control );
        initChildren.Add( control );
        
        // setup backpointer
        control.parent = this;
    }
    

    virtual function OnGUI( screenPosition : Vector2 )
    {
        // check visibility
        if( !visible )
            return;
            
        // what the...
        if( children == null )
        {
            Debug.Log( "No children and initState=" + initState + ", initChildren=" + initChildren );
            return;
        }
        
    }
}

So I really need some insight as to what the proper procedure here should be; how do I protect my objects against whatever comes along and strips the lists away? Implement some deep serialization? Derive from another base class entirely?

I tried a putting a list field in a MonoBehaviour, and saw the same result. Tried deriving from ScriptableObject, also same result, even though the docs indicate they’re for holding data.

I made a small test project that illustrates the problem I’m having.

Just run it normally, there’s just the main camera in the scene, use the “Dump” context menu on TestComponent to see that the component has a single element in the “someList” array.

Open up TestComponent.js make a change and save it. Go back to Unity and you’ll see the Update() is printing out “Lost the list! someString=initialized”, which indicates the array is now null (!), but the string is the correct value.

I’m running Unity iPhone v1.0.1f3.

129561–4823–$object_destroyed_bug_418.zip (476 KB)

don’t assign anything to arrays / fields where you intend to have the stuff set in the editor.

Well yes, I’m not quite at the point of needing to edit yet, more just wondering why when I compile my script it sets my list field to null (not just an empty list).

The attached demo doesn’t do the clearing either though, and I still see the behaviour. Hm.

So here’s what I’ve discovered about which types are nuked, and which survive the edit-and-continue cycle:

var someString : System.String = "empty"; // safe
var someSpecificBuiltinArray : SomeObjectType[]; // safe
var someGenericBuiltinArray : Object[]; // unsafe
var someArray : Array; // unsafe
var someArrayList : ArrayList; // unsafe
var someObject : SomeObjectType; // safe

So there seem to be two restrictions on building a data model for our games (and having it survive the edit-and-continue cycle):

(1) You cannot use generic reference types (Object, Object[ ], Array, ArrayList, etc.). Specific reference types are fine (MyObjectType, MyObjectType[ ]).

(2) Your type referenced-by graph cannot contain cycles. That is, you can’t ever potentially have a case where following the subfields of an object leads back to itself. I’m inferring this based on the fact that the editor crashes when you have cycles in your type references. (Two exceptions: System.Object and ScriptableObject fields, see below).

So here’s my best guess for what’s going on:

When I submit a new script, it has to worry about migrating existing objects in-memory to the new field format (since fields can be added/removed/retyped). This means internally knowing where all references to the given object are so they can be changed to point to the new object. The Mono runtime knows where all pointers are because you gave it your class definitions, and promised not to make “opaque pointers” :slight_smile:

In addition, it means transferring state from the object you’re replacing to the new object with the additional fields. Wild guess: Unity does this by serializing the old object, allocating the new object, deserializing into the new object, and applying pointer fixups to the new location. This shouldn’t, by design, cause any untoward side-effects.

However looking a little closer at (2), it seems related to an assumption needed for struct-type serialization (i.e. that you can just copy the bytes in memory directly to disk, adding a header for type info). If you treat System.Object as a struct and just blindly serialize the object and all subobjects as structs, then having a cycle would mean a potential infinite loop at runtime. So I’m guessing this is how they serialize, and this gives us quick code that serializes builtin arrays very quickly.

This being the case, Unity ignores System.Object references when serializing in order to adhere to (2) and avoid infinite loops in its fast serialization. This restriction seems to apply even for serializing objects during the edit-and-continue cycle (!).

I dunno, it would be really useful to opt-out of the potential optimizations for this case with a per-field “EmbeddedObjectAttribute”. This is just an object that’s reference-checked for cycles during serialization just like ScriptableObject, and can appear in the inspector as drop-down objects (not the ones that point to themselves in the scene/project views). Alternatively, the attribute could sit on the ScriptableObject-derived class. Unreal3 does this! :smile:

It also forces a little manual work to support large built-in arrays (shouldn’t be recopying those, basically implement your own ArrayList). Although with generics that problem goes away.

Well, it would be nice to get an answer from the devs on this, just to see if I’m hallucinating these problems or if this is really how it is.

I am stuck with the same problem.
Is there any comment from the developers?

@unitize: did you manage to find a solution for your problem yet?

Well, I haven’t heard from the devs on this one, so I just made some functions to handle insert/remove/find from builtin arrays of ScriptableObjects. So as long as your list objects derive from that class, you’re OK.

Its a little messy since I’m re-creating the array for each insert/add, I suppose the better solution is to write a container class that hides all the gory details, i.e. makes a linked list or array chain. But it probably doesn’t matter until 20+ elements.

AFAIK, there’s no workable solution for arrays of Object-derived objects, due to the struct-style serialization enforced on them during edit and continue.

Hope that makes sense.

it makes sense, i can back up your observations.

The problem is: on Windows the engine crashes as soon as too many data is stored directly within the scene - and thats where you get in trouble trying to have 12000+ waypoint objects derived from ScriptableObject.

I need to save my waypoints outside by serializing them independently, but I cannot do that because the waypoints need references to each other, which is not possible to do.

maybe you have any tipp for me?

Oh, wow I haven’t run into that one. Maybe try re-generating them at runtime if you can’t serialize them? Or if you really need to deserialize them you could derive from System.Object and add an extra “id” field, which is the persisted reference to another object. Then after loading, you can fixup the array references using the id.

Out of curiosity, have you tried a small test project to double-check your assumptions about 12000+ ScriptableObjects?

Could you file a bug on the 12000+ crash issue?
(Help->Report a Problem)

You do not need to inherit from System.Object, everything in .net inherits from System.Object.

You’re right in that right now unfortunately serializing some “basic types” is not supported. I think you can indeed work around it by making sure you refer to types you created yourself instead of to System.Object[ ]

Good luck, Lucas

Then you are doing something seriously wrong.
Each waypoint should have a unique ID (commonly an int or long)
All other linked waypoints are not linked through real pointers (they are invalid the moment the program is shut down) but linked through storing their IDs in a linked list / dictionary / hashmap

These IDs are used to request the actual node reference from a node manager or your path manager, depending on how the whole thing is built.

If your game requires the real references, expect to run into very very ugly problems

I don’t think it was mentioned, but from my observations, members must also be public to be serialized.

That means no read-only patterns like this:

	[Getter(map)]
	_map as NStateMap

Or this:

	_map as NStateMap
	map as NStateMap:
		get:
			_map = NStateMap() if _map is null
			return _map

;-(
(That’s me right now.)

System.Type is not serializable either.

I’m working around the issue by storing it as string and having a property that gives the System.Type back.

Is there a wiki page for this info? If there isn’t, there probably should be one (so I don’t keep making notes to myself on the forum ;-D).

It seems custom Genericized classes do not serialize as well.

For example, this works:

[Serializable]
class SerializableType:
	# hand-edit at your own risk!
	public typeName as string
	
	type as Type:
		get:
			return null if String.IsNullOrEmpty(typeName)
			type as Type = Type.GetType(typeName)
			return type
		set:
			typeName = value.FullName

This does not; it gets nulled out when serialized, then unserialized:

[Serializable]
class SerializableDerivedType[of T]:
	# hand-edit at your own risk!
	public typeName as string
	
	type as Type:
		get:
			return null if String.IsNullOrEmpty(typeName)
			type as Type = Type.GetType(typeName)
			assert type.IsSubclassOf(T), "${type.FullName} must be derived from ${typeof(T).FullName}"
			return type
		set:
			assert value.IsSubclassOf(T), "${value.FullName} must be derived from ${typeof(T).FullName}"
			typeName = value.FullName

I hope this helps anyone in the future who comes along this thread. (I can provide a C# version on request.)