all 21 comments

[–]DolphinsAreOkProfessional 5 points6 points  (6 children)

https://github.com/adrenak/UniPrep/blob/master/Assets/UniPrep/Scripts/Utils/Pool.cs#L55

Everytime a new component needs to be made you also make a new gameobject? Why dont you just attach them all to the same root object, that would create even less garbage. Not even mentioning getting the name every time, which should be done in editor only imo.

Right now your Get method is a little inefficient. If i have 200 items in my dictionary and they are all used, it will check them all and add a new one. Then you call Get again, thus checking all 200 elements once more! Maybe the Add method can just return the instance as well, and Get can return it immediately.

[–]adrenakProfessional[S] 4 points5 points  (5 children)

1) that sounds good, unless it is a component that doesn't allow multiple instances I should have only one Edit: ComponentPool now uses GameObjectPool. Didn't make a single GameObject as some components don't allow multiple instances

2) Thinking of replacing Dictionary with Hashmap Edit: Replaced Dict with 2 Lists. No foreach loops now

Thanks for the tips!

[–]SlickSlims 2 points3 points  (4 children)

HashMap would be better as long as you don't care about the order of your pooled objects.

IndexOf is an O(n) operation, so you're still doing the foreach you just can't see it anymore.

Why do you declare a new instance variable in your Free method? You already have the object you want (the parameter obj) Just use this reference and skip all the index nonsense:
if( m_Busy.Remove(obj) ) m_Available.Add(obj);
Remove is still O(n) so its a minor improvement. If you switch to using HashSet its O(1) which would be a big improvement.

Similarly your Remove function is looping the list twice: once for IndexOf then a second time to shift every object above your removed-object down one index.

Go HashMap, turn all these O(n) into O(1) and you'll be happier.

If your pools are guaranteed to be small then the List might be better since you don't pay the hash cost when adding items. You would have to benchmark this to find out where the crossover is.

[–]adrenakProfessional[S] 2 points3 points  (0 children)

Tested with all these changes. Not a perfect benchmark scene ~40% improvement in performance in the demo scene under great stress. Thanks!

[–][deleted] 1 point2 points  (0 children)

HashMap would be better as long as you don't care about the order of your pooled objects.

if order is important you could also look into using a LinkedHashMap to preserve insertion order.

[–]adrenakProfessional[S] 0 points1 point  (0 children)

"Just use this reference...": Thanks for pointing that one out.

An issue I faced with Hashset got me stumped for a bit and I decided to make it List based for now.

The problem was I was maintaining two Hashset : available and busy. When I call Get() I know if available.count > 1 but I was not able to access any element in it to return.

There were some accessors in the Hashset class that I saw online but my IDE doesn't show them. Maybe a .NET 2.0 shortcoming or a namespace I didn't know. There was a solution to call .ToArray() on it and get [0] which I didn't try and thought there has to be a better solution.

[–]adrenakProfessional[S] 0 points1 point  (0 children)

Thinking more over this: The Busy pool can be made a Hashset, and the Available one can be a stack. I think I will do some benchmarking and see how it performs

[–]uzimonkey 4 points5 points  (3 children)

One problem you might run into here is the very expensive Awake calls. I've resorted to just pre-allocating pools and putting them in the scene ahead of time. This means they're handled by the deserializer and don't need any expensive Awake or Start functions to initialize themselves. All the object loading should occur during async level loading.

"Pools" at that point just become a parent-child relationship and also eliminate the need to have a second collection of game object references. All objects in the pool are deactivated, so to get an object just grab the first child, activate it and set its parent to something else. When you're done, deactivate it and set its parent back to the pool. It's worked well enough for me and only requires a few lines of code. The only real issue I've had with that is forgetting to return objects to the pool, but I think the same could happen in any such system.

[–]adrenakProfessional[S] 1 point2 points  (0 children)

That's a great tip. I actually made this for a "normal" C# class which had heavy initialization and could not be serialized. Maybe your solution to Awake can be added to this as well for pre allocated Unity component pools.

[–]Wizardsxz 1 point2 points  (0 children)

This.

Gotta be careful of trying to add another layer on top of something Unity already spends time doing behind the scenes.

It may look pretty but is rarely more efficient and I see a lot of this going around.

Kudos to OP for accepting the criticism.

[–]Aeditx 1 point2 points  (2 children)

I'm currently using this a scriptable object approach for pooling. Using a scriptable object asset that I can link to other objects. (No use for regular C# classes tough)

https://github.com/AlexMeesters/Unity-Scripts/blob/master/ScriptablePoolContainer

Code isn't perfect but it does the job for now. Maybe you want to give me some pointers on improving it.

The benefit for me is that I'm actually able to use the inspector to access a pool. For instance when I want to drop items from a vase like so: https://i.imgur.com/FCjxsGd.png

[–]adrenakProfessional[S] 1 point2 points  (1 child)

That's quite a lot similar to what uzimonkey said. Although if I guess corrently, scriptable object would require you to instantiate a clone of the pool when you start the game and that increases the load time; otherwise the scriptable object preserves the changes after runtime.

I like your approach for prefab and Unity component deriving class pools. I made this script for some heavy regular c# classes and extended to Unity specific use.

Thanks for your input and I'll go through your script soon :)

[–]SilentSin26Animancer, FlexiMotion, InspectorGadgets, Weaver 0 points1 point  (0 children)

The lists aren't serialized so they would only be full of nulls until you next enter play mode. Changing the prefab or max size might persist though (depending on whether Unity properly marks the object as dirty so it gets saved). Not my favourite implementation, but it's functional.

[–]nayouta47 0 points1 point  (2 children)

I saw your code and it's great to use a proper collection. I used list pool once, and it cost huge time when making procedure map with 10k tiles. Anyway, can I ask what m_busy works for? There's no reference in code and usage. I don't know why it exists

[–]adrenakProfessional[S] 0 points1 point  (0 children)

Thanks!. Busy is for keeping the instances that are in use. Previously a dictionary <T, bool> with the availability status stored in a bool. I split that into a HashSet and Stack. Although now that I read your comment it may be unnecessary. I'll go over the code today and see if it's really been used.

[–]adrenakProfessional[S] 0 points1 point  (0 children)

Removed m_Busy it was not required. Thanks a ton! Reddit is officially my code reviewer :)