New Scripter Question: Creating Modifiable Indexes of Class Objects

I’m very new to scripting. I can do your basic operations and I could do what I’m trying to do in a very inefficient fashion, but there must be a better way.

I’m creating a script that will be attachable to rigidbody objects that will monitor physical properties of that object by relative coordinate systems. For example, it will record velocity, acceleration, impulse vectors relative to an arbitrary down vector or relative to other objects in the game world with the same script. The problem is, I’m aiming to make this script extremely versatile, wherein I’d like to incorporate it into more than one attempt at a first game. Because I want to make it, potentially, possible to attach this script to 10,000+ objects, it’s completely unreasonable to perform all these vector functions in the same frame for all 10,000 objects. Hence, I need a script manager that prioritizes and manages updates for each object.

My goal is to have an empty object in each scene with the script manager attached. Each individual script will register with the script manager and be assigned an ID number and the script manager will then decide which script gets priority and how often its internal data is updated. This is where my problem lies.

My instinct as an amateur is to create in the script manager a class for each script which will manage the priority and ID number of the script. This isn’t particularly hard unless you have to unregister scripts, for example, if an object is destroyed, the script manager needs to be told that it doesn’t need to worry about that script anymore. When I delete a class object from the array, the array index of all the other class objects will change, which will make it difficult to manage the ID numbers of the scripts. I don’t want to sort through the entire index of 10,000+ class objects in search of which object to destroy - there must be a more efficient way to do this.

In short, I want my object, we’ll say “Ball” with ID Number “10005” to just tell the script manager “unregister number 10005” and I don’t want the script manager to sort through 10,000 objects to find that ID number - nor do I want to assign new ID numbers to every class object in the script manager’s registry every time one of them unregisters.

Like I said, I’m pretty new at this stuff, so there’s probably a great way to keep indexes of objects that are easily accessed that I’m just not aware of.

I’ve considered making the script manager just take the total number of registered objects, say 10,000. Then keeping track of “halfway” points. So, it knows that 10,000 objects were registered. It knows the half-way point is 5000. So, at Array Index 5000 should be Object ID number 5000. But object 900 and object 1045 just unregistered. So, now there’s a total number of 9998 objects, the halfway point is 4999 and at point 4999 the Object ID is now 5001 so any object ID over 5001 only has to search through the latter half of the class object array. This subdivision technique could subdivide multiple times which would make searching the array much faster - but I just don’t know if this is the best technique.

I hope this makes sense and nobody is getting too much of a laugh out of my programming ignorance.

Here’s an example of what I’m trying to do - where I don’t want to sort through the entire “hashtable” to find which current array index corresponds with which Object ID:

While it’s not “exactly” what you want, it’s rather close:

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

/// <summary>
/// Provide an automatic storing of instance for faster type retrieval. 
/// Much faster then Unity "FindObjectsOfType" which iterate over all object.
/// In a scene with 10K+ object, it can be rather slow.
/// It stores all the type hierarchy of the item.
/// Ex.: GetInstances(typeof(Character)) or GetInstances(typeof(Player))
/// Add [Registerable] on types you want recorded.
/// </summary>
public abstract class RegisteredMonoBehaviour : MonoBehaviour
{
    private static Dictionary<Type, HashSet<RegisteredMonoBehaviour>> instanceCollection = new Dictionary<Type, HashSet<RegisteredMonoBehaviour>>();
    private static Dictionary<Type, HashSet<Type>> typeTree = new Dictionary<Type, HashSet<Type>>();

    private bool registered = false;

    protected virtual void Awake()
    {
        Register(this);
    }

    protected virtual void OnDestroy()
    {
        Unregister(this);
    }

    /// <summary>
    /// Returns all existing instanced of a specific registerable type.
    /// Returns nothing is no instances exist or if that type is not registerable.
    /// </summary>
    public static RegisteredMonoBehaviour[] GetInstances(Type type)
    {
        HashSet<RegisteredMonoBehaviour> set;
        if (instanceCollection.TryGetValue(type, out set))
        {
            RegisteredMonoBehaviour[] instances = new RegisteredMonoBehaviour[set.Count];
            set.CopyTo(instances);
            return instances;
        }

        return new RegisteredMonoBehaviour[0];
    }

    private static void Register(RegisteredMonoBehaviour item)
    {
        if (item.registered)
        {
            Debug.LogError("An instance is trying to be registered twice.");
            return;
        }

        Type type = item.GetType();

        foreach (Type registerableType in GetTree(type))
        {
            HashSet<RegisteredMonoBehaviour> instances;
            if (!instanceCollection.TryGetValue(registerableType, out instances))
            {
                instances = new HashSet<RegisteredMonoBehaviour>();
                instanceCollection.Add(registerableType, instances);
            }

            instances.Add(item);
        }

        item.registered = true;
    }

    private static void Unregister(RegisteredMonoBehaviour item)
    {
        if (!item.registered)
        {
            Debug.LogError("An unregistered instance is trying to be unregistered.");
            return;
        }

        Type type = item.GetType();

        foreach (Type registerableType in GetTree(type))
        {
            HashSet<RegisteredMonoBehaviour> instances;
            if (instanceCollection.TryGetValue(registerableType, out instances))
                instances.Remove(item);
        }

        item.registered = false;
    }

    private static HashSet<Type> GetTree(Type type)
    {
        HashSet<Type> tree;
        if (!typeTree.TryGetValue(type, out tree))
        {
            tree = new HashSet<Type>();
            ValidateType(ref tree, type);
            typeTree.Add(type, tree);
        }

        return tree;
    }

    private static void ValidateType(ref HashSet<Type> tree, Type type)
    {
        if (type.GetCustomAttributes(typeof(RegisterableAttribute), false).Length > 0)
            tree.Add(type);

        if (type.BaseType != typeof(RegisteredMonoBehaviour))
            ValidateType(ref tree, type.BaseType);
    }
}

Every instances of your “type” can derived from this and be tagged with the following attributes:

/// <summary>
/// Define a type that derive from RegisteredMonoBehavior that can be stored.
/// Derived types that do no uses this flag may be stored as a sub-type.
/// An instance is stored in all flagged type and sub-type.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class RegisterableAttribute : Attribute { }

It is a totally generic solution into storing instances of a specific type. You can probably modify it to make it more specific to your case.

Finally, you would do something like this:

[Registerable]
public class MyScript : RegisteredMonoBehaviour 
{
    public void MyUpdateMethod() 
    {
        //Do stuff
    }
}

public class MyScriptManager : MonoBehaviour
{
    private const int OFFSET_MAX = 5; //This is how many indexes are skipped every frame.
    private int offset = 0;

    private void Update()
    {
        RegisteredMonoBehaviour[] collection = RegisteredMonoBehaviour.GetInstances(typeof(MyScript));

        for (int i = offset; i < collection.Lenght; i += OFFSET_MAX)
             ((MyScript)collection[i]).MyUpdateMethod();
        
        offset++;
        if (offset >= OFFSET_MAX)
             offset = 0;
    }
}

Anyway, I haven’t test this, but it’s one way to approach the problem.

Yes, in your final script you get what I’m trying to do. Make it automatically change the frequency of updates for scripts. However, this manager is only going to be keeping track of a single script which will be attached to anywhere from 1 to indefinite objects (probably will have an inherent max in the script). Hence, it will be able to evaluate how often to tell each object to use its update method. Basically, the individual scripts will be able to talk to one another and get data from each other as the objects encounter one another in the game world, but the script manager will be in charge of telling them how often they can run their update method based on a priority rating which will be assigned to each gameobject’s script.

I’m just trying to figure out the most efficient way for the script manager to take registration and deregistration calls from the objects and still keep track of where the object is in the script manager’s internal array by its “Registered ID #” without sorting through the entire index each time given that objects will be registering and deregistering throughout runtime.

So, the script manager has a list of objects which are priorty 1 which will update every n frames and objects which are priority 2 which will update every n+3 frames or something like that.

I suppose it’s probably better to not assign my own ID numbers to objects and just use the intrinsic “GameObject” value Unity has. I just preferred to have my own class based on my own ID numbers to provide more versatile behavior in the scripting.

For example, I would want a high priory script to request the script manager to upgrade the priority of an object its “targeting” or “analyzing” or whatever due to the fact that the two objects are interacting with one another. e.g. The “player” asks “moving cube” its assigned “script manager ID #,” the player script then asks the script manager to upgrade moving cube’s priority based on its assigned ID #, and the script manager says “I know exactly which GameObject you are referring to by its assigned ID #, no need to search through a 20,000 long array to find it.”

You don’t get it… Registration and Unregistration is automatic in the RegisteredMonoBehaviour class. You don’t have to track anything.

Also, it registers a hierarchical tree of classes.

Example;

[Registerable]
public abstract class Updatable : RegisteredMonoBehaviour 
{
    public abstract void InternalUpdate();
}

public class MyScriptA : Updatable { }

public class MyScriptB : Updatable { }

public class MyScriptC : Updatable { }

and if you do:

RegisteredMonoBehaviour[] updatables = RegisteredMonoBehaviour.GetInstances(typeof(Updatable));

You’ll get every single instances of MyScriptA, MyScriptB and MyScriptC.

EDIT: Is your issue with priorities?

You’ll get every single instances of MyScriptA, MyScriptB and MyScriptC.

EDIT: Is your issue with priorities?[/QUOTE]

My issue is less complex than that specifically. I’m just trying to figure out a way for a script to keep an array of class objects based on an arbitrarily determined ID number while simultaneously adding and removing those class objects based on their assigned ID number rather than directly by the array index.

So, I have cube, sphere and pyramid.

“Empty” has a script manager.

Cube registers with Empty and gets the ID number 1
Sphere registers with Empty and gets the ID number 2
Pyramid registers with Empty and gets the ID number 3

Empty has recorded information about Cube Sphere and Pyramid (like health, acceleration, and number of apples inside) in an array of class objects.

Cube has array Index 0, Sphere has array index 1, Pyramid has array index 2.

The class objects also contain the ID number of Cube, Sphere and Pyramid.

Sphere gets killed by Pyramid, so Sphere tells Empty to unregister Sphere.

Sphere’s class object is deconstructed. Leaving Cube at index 0 and Pyramid at index 1.

Now Tetrahedron registers with Empty and gets assigned Sphere’s old ID number 2. But Tetrahedron, because he is appended to the array, is at index 2.

So now… Cube is ID number 1 and at Index 0, Pyramid is ID number 3 and at Index 1, and Tetrahedron is ID number 2 and at index 2.

Now Cube asks the Empty “what is the health of Tetrahedron which has Object ID number 2?”

Empty has to sort through the entire list of class objects to find which array index has object ID number 2.

I’m trying to avoid the last step considering I might want 10,000 objects registered and figure out how “Empty” would know pretty much right away which array index is object ID: 2.

Like I said, I’m pretty new at this, so I’m probably missing something completely obvious. Unfortunately you’re throwing me C# which is not what I have done in Unity - only JS. I understand most of it, but not really well.

Maybe I should just do more research before I start asking questions like this.

Essentially, script manager is the totalitarian overlord and anything one object wants to know about another object has to ask the script manager first based the the number the script manager assigned that object. But since objects keep registering and unregistered with the script manager, the array the script manager keeps will get its index out of order with the ID numbers it is assigning. I’m just trying to figure out the most efficient way to find which object ID exists at which index in the array.

However, maybe I’m trying to be too efficient by reducing searching through arrays and there’s really nothing else that can be done to keep easy track of something like that.

Basically, I’m trying to create a hash table of class objects. I guess I could have said that in the first place. And, now that I thought of it like that, I see that that is possible… so thanks for your help. I haven’t done this for a while so when I sat down to start working on this, I just hit a snag thinking “I have no clue how to create my own index for things that can be easily modified and browsed by an arbitrary label.”

In other word, you don’t need “IDs”. An ID is used when you want to retrieve a specific something without knowing the real identity of what you seek, and you only have an address of it. In your case, you only want a collection of object that meets a criteria. Hash tables (or HashSet) has no index because the position of the item in the collection is not important. What’s important is that the object is part of the collection. It’s also why HashSet are so damn fast.