- Home /
Material leak workaround?
In my current project there seem to be some material leaks which I can't get rid of in the first place since I don't want the user to have to create a Material asset every time he wants to assign a new one.
My workaround so far has been that I call a function to destroy all materials I used in the end:
...
foreach(Material material in matList){
if(Application.isEditor){
GameObject.DestroyImmediate(material);
}else{
GameObject.Destroy(material);
}
}
...
This works fine and stops the leaks but if the user used a Material asset instead of creating a new one per code I get the following error on destruction:
"Destroying assets is not permitted to avoid data loss."
Since I don't want to destroy the Material asset but only those Materials that got created by code I can't just call
DestroyImmediate(material, true);
I tried checking if the material is an asset before destruction by using
if(!AssetDatabase.Contains(material)){
DestroyImmediate(material);
}
which worked as long as I was testing in my custom editor window or using "play" in Unity. But since it's an editor function I can't build it this way.
Is there any other way to get rid of the material leaks? As I said before, I can't just stop using Materials created by code. Or is it possible to suppress the "Destroying assets is not permitted..." error? Since aside from the annoying message that works really well.
Thanks in advance
delstrega
Answer by whydoidoit · Jun 23, 2012 at 09:59 AM
So what you need to do is write a script that does this:
MaterialManager.js
import System.Collections.Generic;
import System.Linq;
private static var existingMaterials : Dictionary.<Material, Material>;
function Awake() {
if(existingMaterials.Count == 0)
{
existingMaterials = Resources.FindObjectsOfTypeAll(typeof(Material)).Cast.<Material>().ToDictionary(function(m){return m;});
}
Destroy(GameObject);
}
static function DestroyMaterial(material : Material) {
if(!existingMaterials.ContainsKey(material)) {
DestroyImmediate(material);
}
}
When you want to destroy a material do MaterialManager.DestroyMaterial(myMaterial);
this will only destroy materials that were created by the user.
Thanks whydoidoit, this seems to work fine if I can make sure that the manager's Awake() is called before any material creation. If my "GetInstanceId() < 0"-workaround fails on a user machine I will use your manager for sure.
@Bunny83 - arghhh and it always takes me 2x to write .js ;)
@DelStrega - actually the way to be sure in that case is to make it a real game object, use singleton pattern and save the list of objects in the scene - always works then (it's how Unity Serializer tracks prefabs and scene references). Seems like overkill - but you are right, you would have to use script execution order settings to get this to try and run first :)
Haha, yeah I'm using C#, but thanks for taking the time to .js a little here ;D True, using a singleton GameObject should do the trick. But in all honesty - just having a way to get rid of that error message would be soooo much easier ;)
Answer by delstrega · Jun 23, 2012 at 09:38 AM
After thinking about this stuff for hours now I discovered something interesting: At least on my machine, materials that are created by code with
Material m = new Material(...);
had an InstanceId (returned by .GetInstanceId() ) that was negative, all material assets had a positive one.
I'm quite sure that this is no coincidence (correct me if I'm wrong here). This seems to be an easy solution to check whether a material is in fact an asset or created by code only. The destruction code now is:
...
foreach(Material material in matList){
if(Application.isEditor){
if(material.GetInstanceId() < 0){
GameObject.DestroyImmediate(material);
}
}else{
if(material.GetInstanceId() < 0){
GameObject.Destroy(material);
}
}
} ...
It works perfect on my machine but I don't know if it works everywhere. It would be great if anyone could check if .GetInstanceId() returns a negative value for "= new Material" and a positive one if an asset is used.
Sorry whydoidoit, I didn't know. But since my "comment" is a solution of sorts too it might not be that problematic here, i hope.
That's really an interesting behaviour. That could be useful in other cases as well.
I think it's ok as answer, but you might want to remove the "comments" from the answer and repost them on the appropriate post as comment. Fell free to edit your answer ;)
Did what you suggested Bunny83 - hope it's fine now ;) On a sidenote, did anyone check on GetInstanceId's behavior? Does it return negative values on your machine too?
There's none at the moment - I'm in the midst of actually creating it by now ;D It's something I used for a game project of $$anonymous$$e which I thought could be useful for others too. At the moment I'm trying to make it foolproof and user friendly as well. That's why it's so important for me to not restrict the end user too much by forcing him/her to use material assets only for example. On a sidenote though, I'm a she ;)
True, true. :) Lots of problems I encountered were only introduced since I couldn't be sure how the end user would use things. Didn't have that experience before, since I always only developed stuff for myself. It's a great coding practice though and makes you really think hard about stuff. So regardless of how things turn out in the end, it's a nice learning experience :D
EDIT: I asked in the forums, whether this behavior was intended and it seems that it is: http://forum.unity3d.com/threads/140978-GetInstanceId%28%29-behavior-on-different-platforms-need-dev-confirmation.
So anyone trying to check if something is an asset might have an easy solution at hand with "GetInstanceId()" :)
Answer by Loius · Jun 22, 2012 at 09:02 PM
It sounds like it might be a good idea for you to create code versions of all the asset materials during your initialization - that way you don't have to worry about whether a material supports some action or not, and your user is "more guaranteed" to have full control over the materials.
Thanks, Vicenti. That would probably be a solution, but since I don't know beforehand how many material assets the user has in his project that could be somewhat taxing.
Answer by viktor1406 · Jun 22, 2012 at 09:20 PM
so (i hope this isn't a dumb answer) if you code your materials that when they are created they get a certain tag "CreatedByCode" for exemple and when you end your script do it like this DestroyImmediate(Tag "CreatedByCode")
Thanks viktor1406! Seemed like a great idea at first but actually this way I still would end up with the same problem of not knowing WHEN to set this tag. I don't want the user to set this tag by himself since this would create an unnatural workflow and might be difficult for user that aren't that versed in scripting.
wait i think i got a solution, your problem is that you don't want to delete the asset and you don't want to have tags on the materials taht are gonna be set by the player itself so do it like this: give your asset an tag in the beginning "NotDestructeble" for example and just destroy everything beside that
This would certainly work if it was just me using the code.
But actually my problem is, that a user calls my function which takes a $$anonymous$$aterial as a parameter. In my function I will not know if the passed $$anonymous$$aterial is an asset or not, so the user would have to add such a tag before calling the function. But by using "GetInstanceId()" (see my answer) I have found an easy way of checking if the the supplied $$anonymous$$aterial is an asset or not.
Thanks again though, viktor :)
ok i think i misunderstood it a bit, srry for bothering you :)
No need to be sorry :) Your post really got me thinking :D
Answer by Bunny83 · Jun 23, 2012 at 10:07 AM
You can use AssetDatabase.Contains in your script. You just need to wrap UnityEditor stuff with
#if UNITY_EDITOR
//editor stuff
#endif
whereever you use something from the UnityEditor namespace.
So, If you have a "`using UnityEditor`" you have to wrap that as well or use the full class name "`UnityEditor.AssetDatabase.Contains`"
This way you can put editor functionality into your runtime scripts and you're still able to build. Keep in mind that your script has to work with the #if sections removed. Also keep in mind there's also an #else
If I understand this correctly that would mean, that I would be able to build, but unable to use "AssetDatabase.Contains" at runtime. I will try to wrap my head around this when my "GetInstanceId() < 0"-workaround turns out to be pure coincidence. :)
Your answer
Follow this Question
Related Questions
Texture/Materials are not loading ,Textures are not showing up on my materials 0 Answers
Material tiling on imported fbx model doesn`t fit 0 Answers
Cant add bullet trail material to script - > Bullet Trail Prefeb 0 Answers
Gameobject is destroyed even though I don't actively destroy it (or it's parent) 2 Answers
Errors while assigning material to other object's by scripting. 1 Answer