2

I have recently been trying out object pooling in unity to speed up the instantiation of several game objects at once.

However, since these are fairly complex objects I need to reset them when they go back in the pool.

I read that using ScriptableObject might be a good way to store the default values for an easy reset. But in order to do that I need to load a fresh ScriptableObject at runtime to store the actual values of the object.

So in pseudocode, i'd have a class with public MyScriptableData data and public MyScriptableData defaults

1) create new pooled object with default ScriptableObject data = defaults;;

2) do stuff that changes values of scriptable object during lifetime of object

3) deactivate, and then return pooled object to pool, resetting the scriptableObject to its default (data = defaults; again).

I have 3 main questions:

A) I'm not sure how to actually implement this. It seems to me, in step 2, the default values would be changed. Therefore resetting to defaults would do nothing. I thought about creating a new instance of my scriptable object using

data = ScriptableObject.CreateInstance<MyScriptableData>();

but then how would I copy in the default values from defaults, ensuring in never changing the defaults? I would like for the defaults to be editable in the unity editor as an asset.

B) If I use CreateInstance, will the performance be bad? The whole reason i'm doing this object pooling is to reduce the performance costs of object instantiation. I'd hate to reintroduce slow code by instantiating scriptable objects.

C) Is this approach alright? or is there a better way to reset objects before going back into the pool?

EDIT based on some answers: I already have a setup that has a long list of fields, and then stores the default values of theses fields in a dictionary. But I found every time I wanted to add/change/remove a field, I had to change code in several spots

ATTEMPTED SOLUTION (But wrong, see below): I created an extension method for ScriptableObject:

using UnityEngine;
using System.Reflection;

public static class ScriptableObjectExtension {

    public static T ShallowCopy<T> (this T orig) where T : ScriptableObject {
        T copiedObject = ScriptableObject.CreateInstance<T> ();
        FieldInfo[] myObjectFields = orig.GetType ().GetFields (
                                         BindingFlags.NonPublic | BindingFlags.Public |
                                         BindingFlags.Instance);

        foreach (FieldInfo fi in myObjectFields) {
            fi.SetValue (copiedObject, fi.GetValue (orig));
        }
        return copiedObject;
    }
}

FINAL SOLUTION:

The above script worked to clone scriptable objects, however, it appears I was going down the wrong path with that solution.

Several people below pointed out that pooling isn't that important in unity for most applications. I had originally tried to put pooling in because my framerate according to the profiler was around 30-15 fps, and I thought pooling would help improve that.

Based on the comments I dug a bit deeper and found that there was a process called LogStringToConsole. I said to myself, could this be as simple as my Debug.Log statements slowing things down!? I deleted them and the huge spikes went away. Apparently Debug.Log causes huge performance problems. Now I'm well above 60fps. Because of this I have decided not to pool those objects (But I still use pooling on simpler objects in another scenario that are being spawned several times per second). This means I don't need to worry about scriptable objects here at all. I now have and instantiate that loads a prefab and an Init method to set things up, and the objects are destroyed when they are used up.

When I went back to using instantiate/destroy, I didn't notice a significant change in performance. Thanks for all the responses!

Adam B
  • 3,662
  • 2
  • 24
  • 33
  • this is bizarre Adam. don't use "ScriptableObject". just save the values in one or two lines of code. – Fattie Jun 16 '16 at 03:00
  • you rarely need to use pooling these days in Unity. you'd be amazed how fast instantiate is. can you state in a sentence what your objects are, and what sort of game it is? – Fattie Jun 16 '16 at 03:03
  • @JoeBlow I am simulating animals that start as a sphere. I add body parts to it procedurally. Each body part is ~10 game objects. Animal can have upwards of 100 body parts. There can be hundreds of animals that are dying and reproducing. So there can be a lot of instantiation. The profiler showed my framerate spiking whenever new animals were created, so I'm trying to optimize both the instantiation and my animal creation scripts (A lot!). It's possible that it's not the instantiation but instead my code causing it, but I figured I'd try to tackle both avenues. Let me know what you think! – Adam B Jun 16 '16 at 03:44
  • @JoeBlow I took your comments to heart and dug a little deeper. I found a weird thing in the profiler called LogStringToConsole. I said to myself, could this be as simple as my Debug.Log statements slowing things down!? I deleted them and the huge spikes went away. Is it normal for Debug.L to cause huge performance problems? Now i'm well above 60fps but with the Debug lines in, I was spiking to around 15. – Adam B Jun 16 '16 at 07:25
  • Hi Adam! Yes! **It's a completely well known funny issue about Unity that Debug.Log (it's obvious if you think about it) spikes performance!!!** that's all it was. – Fattie Jun 16 '16 at 12:08
  • BTW heh .. an old and once very popular post on the unity site .. http://answers.unity3d.com/answers/321797/view.html – Fattie Jun 16 '16 at 12:19
  • Yeah so once I deleted those debug lines and it improved performance, I tried to undo my pooling changes, and the performance was unchanged. I guess this was a case of a noobie overcomplicating things! Now I am back to Instantiating followed by an Init Method. I no longer need to care about defaults because the instantiate can just load from the prefab. Thanks so much for all the comments! – Adam B Jun 16 '16 at 15:41

3 Answers3

4

If you only ever need to reset the object's values whenever it deactivates, couldn't you simply use:

OnEnable()
{
    default = data;
}

OnDisable()
{
    data = default;
}

That would allow you to store / assign default data when it activates, and reset its data back to the default values when it deactivates.

DisturbedNeo
  • 713
  • 2
  • 5
  • 19
2

How about when you created the object, in the Awake() or Start() save the default value that you want to have into bunch of variables or store it inside dictionary,

after that to reset the value just make a method maybe with name called Reset() and assign all the variables with the default values that you have stored before.

e.g.

// method 1
Dictionary<string, object> defaultValues = new Dictionary<string, object>();
int speed = 10;
List<float> scores = new List<float>() {1.5f, 3.4f};

// method 2
SomeClass something = new SomeClass();
SomeClass defaultSomething = new SomeClass();

// and if the type can use const
string sth = "abc";
const string defaultSth = "abc";

void Awake()
{
    defaultValues.Add("speed", speed); 
    defaultValues.Add("scores", new List<float>(scores)); // new list because it is reference type, 
    //and you dont want to store reference to the list because it will be edited during runtime  

    defaultSomething = something.Clone(); // edit, but you need to implement clone by yourself for that class or do something that will make other instance of the class with same value
}

void Reset()
{
    speed = (int) defaultValues["speed"];
    scores = (List<float>) defaultValues["scores"];

    something = defaultSomething.Clone();
    sth = defaultSth;
}

The downside is every instance will store their own default variables occupying memory, you could change them into static or const if you want later on

The other way is you make 1 instance which is used for just storing default value (dont modify this in runtime) and use C# reflection to copy all members value

C# Using Reflection to copy base class properties

Hope this helps

Community
  • 1
  • 1
andiwin
  • 1,552
  • 3
  • 14
  • 27
  • That's what I was doing originally, but found that every time I had to add, remove, or change a field it was a huge mess. That's why I decided to try the scriptable object route. the reflection idea might work well. I'm going to look into that. Thanks. – Adam B Jun 16 '16 at 01:53
  • @AdamB you could make all the fields public and keep a prefab, when you need to reset you just copy the values off of the prefab. – Scott Chamberlain Jun 16 '16 at 02:16
  • @king Your Method 2 won't work. Due to how C# classes works if you `something = defaultSomething;` and then edit `something` you are now editing your `defaultSomething` too. It would only work if `SomeClass` was actually `SomeStruct` – Scott Chamberlain Jun 16 '16 at 02:18
  • "occupying memory" what? how many items do you think of having in a pool? – Fattie Jun 16 '16 at 03:01
  • all you do is have a routine "Reset" or "GetReady". you might have, what, five lines of code which set the values necessary. how many values can you have?? – Fattie Jun 16 '16 at 03:04
  • @ScottChamberlain yeah sorry about that, I edited the code – andiwin Jun 16 '16 at 05:41
-10

Note!!

From about 2014, as a rule you do not need to pool in Unity. Unity drastically improved performance so that, for typical game scenarios, it is not necessary.

Note that the OP eliminated the problem simply by removing their hand-pooling attempt.

In recent years Unity drastically improved their

  • garbage collection

  • memory handling

  • pool-like handling triggering heuristics

  • prefab and instantiation processes

Object creation in video games is completely trivial on modern hardware; typical scenarios like "bullets" only amount to a dozen or so items perhaps in a second; and you have a vast amount of luxury time in future quiet frames to do gc, etc. In the early days of Unity, you would have to write pooling by hand to achieve typical game multiple object requirements such as bullets. Thanks to Unity's efforts, this is now totally unnecessary for typical game scenarios (bullets, multiple NPCs etc).

You can pool if for some reason you want to, but it is totally unnecessary performance-wise, for typical video game needs. 2D or 3D.

Fattie
  • 27,874
  • 70
  • 431
  • 719
  • 1
    Thinking about this further, it makes me a bit upset that I first spent an hour watching a unity tutorial listed on their main learn page, and then 3-4 hours implementing and learning pooling, then another 2 hours or so to undo the whole thing. – Adam B Jun 19 '16 at 07:47
  • 3
    -1 - This is outright wrong. There is no automatic object pooling included in Unity and one has to implement their own object pooling system. – Marc Pilgaard Jan 26 '17 at 14:06
  • 2
    Your statement "After all, prefabs are like a pool" is far from correct. A prefab has no resemblence or functionality similar to an object pooling system. I think you are generalising too much when saying object pooling is not necessary, because it all depends on the circumstances. Use an object pooling system when appropriate because it has advantages compared to not using one. E.g. If you regulary Instante and destroy objects, you are creating a surplus of garbage that will create hiccups when the garbage collector kicks in. – Marc Pilgaard Jan 26 '17 at 14:44
  • 2
    The problem with your answer is its too general. Depending on the game, the assets and the game's requirements, pooling is still relevant. Instantiation is not free, and if the asset you wish to instantiate is complex (e.g. a GameObject with an animator, audio sources, meshes, particle systems so forth), then the instantiation time increases. Do this frequently, e.g. in Update, then you may experience frame rate drops. – Marc Pilgaard Jan 26 '17 at 15:01
  • Instantiation speed is only one factor in why pooling exists. Another has to do with garbage collection. Every time a new object is instantiated there is a new memory allocation that can be minor or quite significant depending on your specific assets memory footprint. When garbage collection occurs, you will often get a stutter/freeze. Creating pools allocates all the memory for those objects upfront so you will experience garbage collection less often than without. This post only speaks to the instantiation factor, so I do get the downvotes. – prototypical Jan 10 '19 at 06:49
  • 1
    This answer is incredibly misleading and extremely bad practice. Garbage collection is still a HUGE issue with the engine that should be avoided whenever and wherever possible if you want your game to run well. This is terrible advice. – Empty Feb 13 '19 at 00:27
  • Unity drastically improved performance of garbage collection so that, for typical game scenarios, it is not necessary to manually pool. Try it and see! – Fattie Feb 13 '19 at 10:41
  • So my space battle game where all the enemy ships are firing multiple lasers amounting to 1000's of lasers coming and going every frame, won't benefit at all from pooling? Ok. – Domarius Jul 20 '21 at 06:39
  • Also OP did NOT fix their problem by "removing pooling" as you claim, they fixed their problem by removing Debug.Log statements. They then stated that removing their pooling had no impact on performance either way. No wonder your answer has so many down votes. – Domarius Oct 15 '21 at 01:00
  • @Domarius , regarding your "laser game" with "1000s" of lasers, you can read the headline which is in bold type. It could not be clearer. – Fattie Oct 15 '21 at 11:46
  • @Domarius - regarding the OPs comments about Debug.Log. They are simply: wrong. (Note that you can trivially test this yourself, in 30 seconds. Try it.) It could be the OP had some monumental mistake, like, generating on the order of 10,000s of log entries a frame. In the **actual situation described in the question** the idea that anyone needs " ' pooling ' ", is just utterly absurd. – Fattie Oct 15 '21 at 11:56
  • @Fattie So you admit enough debug messages can slow down the project, thank you. In fact this is true of any game engine, I do it all the time across different engines, happens by accident. Remove the messages, frame rate goes up. Very easy to demo. – Domarius Nov 28 '21 at 23:44
  • @Fattie No actual situation was described in OP's post other than "several game objects". How much is several? 100? 1,000? 10,000? It's very easy to create a situation where pooling will help. Try it. Create a scene that rapidly creates and deletes high numbers of objects (eg. my "lasers" example). Increase the number till the frame rate goes down. Now implement a pooling technique. Frame rate goes up. Because you're not actually thrashing the RAM anymore by allocating and deallocating memory, just activating & deactivating the objects. – Domarius Nov 28 '21 at 23:47
  • @Fattie Oh look, I searched "object pooling" today for my own reference, and look what the very first search result was! Direct from Unity Learn, Object Pooling and how important it is, updated 2020, https://learn.unity.com/tutorial/introduction-to-object-pooling I will never understand how you came to your conclusion. They even use the same space shooter example as a prime candidate for pooling. – Domarius Feb 10 '22 at 22:18
  • @Domarius (1) unity's documentation is absolutely laughable, so you can dismiss that :) (2) I don't know how clearly I can say . **just try it**. – Fattie Feb 11 '22 at 11:40
  • @Fattie I have, and there are many examples online you can look up of devs pushing the limits of Unity and solving their problems with pooling. So that's great for your small scale game if it doesn't push the limits of the PC. But when you scale up, you will get more mileage from object pooling. Saying "you do not need pooling in Unity" is just not going to be applicable in all cases. And if you're not going to listen to Unity themselves on the topic, I don't know what else to say to you. – Domarius Apr 07 '22 at 01:27
  • @Fattie, the title of your answer is "From about 2014, as a rule you do not need to pool in Unity." Your post is also wrong - OP fixed the problem by removing excessive Debug.Log statements (a common performance issue), not by removing pooling. – Domarius Apr 07 '22 at 08:16
  • @Fattie, I've released a game (HopSquash! https://store.steampowered.com/app/1012450/HopSquash/ ) where the blood particles and their trails effects uses a lot of sprites, and required pooling to not slow down an average system. Each blood particle spawns a sprite every frame which fades away over time, to create the trail effect. My custom pooling solution fixed that problem. Care to weigh in with a link to your project that has tonnes of objects being created and deleted in a very short span of time that didn't need pooling? – Domarius Apr 07 '22 at 08:22
  • @Fattie, https://learn.unity.com/tutorial/introduction-to-object-pooling-2019-3 "Object Pooling is a great way to optimize your projects and lower the burden that is placed on the CPU when having to rapidly create and destroy GameObjects. It is a good practice and design pattern to keep in mind to help relieve the processing power of the CPU to handle more important tasks and not become inundated by repetitive create and destroy calls. This is particularly useful when dealing with bullets in a top-down shooter game." – Domarius Apr 07 '22 at 08:24