- Home /
Wonder about scripts lifecycle (memory) and potentially large methods
Update: Are ScriptableObject the solution to the problem exposed below? Unity's example on ScriptableObject shows fields, what happens if I define methods? What I understood about them is that they aren't compiled with all other cs files, they are treated as data instead?
Update 2: While I'm going with a combination of my xml and Bunny83's parser, at some point I looked at MoonSharp. MoonSharp is a Lua engine written in C# that doesn't depend on native libraries. You can keep a Script instance around and then just remove all references to it, and theoretically this would free all associated resources. In their Unity tips, they ask you to preload all scripts as text assets, but that defeats its purpose, the idea is to load the script at the beginning of each game level, discard or keep around until the next level, then load the script for that level. I didn't try it enough so I don't know If it would work or not but I leaving my experience here.
Initially, I was developing an xml based scripting language to code character dialogs and behavior (like walking following a path, then switch to another behavior when certain condition is met).
An example of how my xml looks (don't waste time trying to understand it, just give it a quick glance and continue reading what's below):
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<scene>
<set name="Default">
<response_random who="$all">
<conditional conditions="one_per_character">
<dialog>
Hello!
</dialog>
</conditional>
<conditional conditions="male">
<dialog>
Yes! I'm with my pal.
</dialog>
</conditional>
<conditional conditions="female">
<dialog>
I'm with my best friend this year.
</dialog>
</conditional>
</response_random>
</set>
<set name="Part00">
<response who="Amelie" ondone="disable">
<dialog>
Hi.
</dialog>
<dialog>
Did you check the box?
</dialog>
<dialog>
The box is in the next room.
</dialog>
</response>
<walking who="Amelie" ondone="repeat">
<point x="3" y="0" z="3" />
<point x="-3" y="0" z="3" />
</walking>
</set>
</scene>
Now, I can use a cs file to obtain the same effect, without having to implement the parsing of the xml file. Also I have the bonus of all the dynamic behavior that is possible if I have the full C# language to check for conditions and use all classes and methods of the engine. Achieving dynamic behavior with xml is possible but you have to create a lot of new tags.
The problem: I don't want the machine code compiled from the cs file to be in RAM at all times during the game. It would be nice to be able to control their loading and unloading, something that I don't see possible. xml are data, text assets, and they are easier to load and unload.
The only way I see to achieve something like that is to to have one MonoBehaviour per scene. At least I believe that unused MonoBehaviours aren't loaded. But I don't sure what happens with static variables or constants. Does Unity compiles all cs files into the same assembly? Does this means they always load, used or not by a GameObject in the current scene?
Because if that is true, then I cannot replace my xml files with hundreds of method calls like this (or I can but it's a bad idea):
public void InitScript () // Each map has its InitScript
{
Dialog("Amelie", "Hi");
Walking(new Vector3 [] {...}, "repeat");
Dialog("Amelie", "Lorem ipsum ...");
// (...) Hundreds or maybe thousands more and only for the first map
}
With languages like C++ I would be able to build a dll per map/zone/level, containing the "script", written in the same C++ that the rest of the game, and use dynamic dll loading to ensure I only have one at a time (see LoadLibrary and FreeLibrary functions from win32). But I don't see how to do something like that in Unity. It appears not possible.
Can I parse JavaScript on runtime? That would solve my problem and save me a lot of time. Something like the standard eval function but from C# code. If this were possible, I can have the script in JavaScript (the weird one Unity supports) but as text assets to load and unload when required. But I believe this is not possible.
Answer by Bunny83 · Dec 04, 2019 at 07:39 AM
Ohh man we have a lot to cover here...
First of all ScriptableObjects are exactly the same thing as MonoBehaviour script, just they are not (do not have to be) attached to a gameobject. So they are simply standalone scripts. Regardless of how the class is used it's of course compiled to the same assembly and of course loaded at runtime into memory. Under normal condition all classes / types you have defined in your script files will be compiled into an assembly and loaded into memory.
The .NET / Mono environment does not allow dynamic unloading of assemblies. The only "thing" that can be unloaded dynamically is a seperate AppDomain. However this will limit the interaction between multiple appdomains (and at least the last time I tried something like that on Android the app crashed).
Compiled code in general isn't that large. Even string constants within the code do not take up too much space since an assembly has token strings which are referenced by an token id. So whenever you use a certain string literal several times in your code, the string will only exist once in memory. Though regardless of that little detail, I wouldn't recommend to "hardcode" such things. Of course it depends on the complexity and requirements.
Unity never really supported Javascript. It was a custom .NET language called UnityScript which had a javascript like syntax but other than that it had nothing to do with javascript. UnityScript also was a compiled language, however it had an implementation of the eval function which allowed dynamic code generation. However that was neither memory nor performance efficient. Note that all the recent Unity versions do no longer support UnityScript. So that's nothing you should care about.
If I were to implement such a system I would probably also go with a common format like xml or json. As for evaluating conditions you might want to have a look at my logic expression parser which could be used just to evaluate concrete conditions (you have to provide the "variables") or you could even use it as "the" scripting language. It allows to add custom functions. Though it generally is designed to output a single "value" (either numeric or boolean). Since the "&&" operator performs an early exit it could be used to construct some basic "if statement" for example:
(someCondition)&&(someMethod() * someOtherMethod())
The two methods "someMethod and "someOtherMethod" are only called when "someCondition" evaluates to true. By default there are only some math functions defined.
Of course instead of xml or json you could use a ScriptableObject to represent your data. This allows you create such object inside the Unity editor and actually ship them with your game or export / load them from json. It's hard to give a real advice here since we know nothing about your project and what kind of features you need. Though it's usually good if you implement some degree of ingame scripting to keep it abstract and capsulated "enough". Otherwise you might open a door for major cheating and manipulations of your game. At worst it might be a target for malware because the more powerful a scripting language gets the more harm it can do if a user finds a way to abuse it.
Your logic parser looks very interesting, I will try to integrate it into my project, (actually, it fits very well, I can put the parseable logic string into a xml tag body or attribute, and let your parser handle it). AppDomain is a messy way of achieving dynamic loading/unloading of assemblies, so is a big no for me, and the fact that you already tried and your Android project crashed reinforces this (I'm targeting Android). Something I want to try, only to see what happens, is AssetBundle. What happens if I put a cs file in an AssetBundle, can they contain cs files at all (or the result of their compilation, separated from the rest of the game)? When unload, can you still create instances of classes co$$anonymous$$g in that cs file? Because of what was said about .Net / $$anonymous$$ono, AssetBundle either cannot have scripts, or if they can, once loaded they stay.