Search Unity

Javascript constructor - the right way?

Discussion in 'Scripting' started by Foxxis, Sep 17, 2007.

  1. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    So, I have a pathfinder that's basically just a class. My "normal" scripts are usually attached to Unity GameObjects which means that I use Instantiate to create the object and get a script reference back.

    Now, if I just want to create an instance of a javascript class (ie. someVariable = new Pathfinder(); ) , where PathFinder.js contains a class declaration and functions, what should the syntax be in the Pathfinder.js in order for the constructor to work?
    If I simply add the constructor:
    Code (csharp):
    1.  
    2. function PathFinder() {
    3.  return(this);
    4. }
    5.  
    I get the error "can't convert PathFinder to void", which seems to indicate it won't accept a return in the constructor.

    If I proceed without a constructor, or with an empty constructor (removing return above), the class works fine. However, the variable that stores the instance in calling functions evaluate to null, so I can't check if it's been created already. Also, I'm not sure how to kill the instance in the right way (assume Destroy won't work here).

    I've searched a bit (in the Unity docs, and on the net) and haven't found a description of javascript contructors/destructors and their use in Unity.

    Help? :)

    [edited for clarity]
     
  2. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    Forgot to mention that something like this works fine:
    Code (csharp):
    1.  
    2. myPathfinder = new Pathfinder();
    3. myPathfinder.Pathfind(start,end);
    4.  
    However, checking if myPathfinder is null after the above will actually return that it is in fact null. So I can create it, call it, but it returns null. That's why I think I'm doing the javascript class thing in an incorrect way.
     
  3. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    Seriously - doesn't anyone know..? I'll admit my knowledge about OOP Javascript is a bit shaky (I'm a C guy), but surely it can be done in a nice way..? :)

    Please? ;)
     
  4. Darko

    Darko

    Joined:
    Aug 28, 2007
    Posts:
    100
    Can you post the checking code?
     
  5. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    Code (csharp):
    1.  
    2. if (activePathFinder==null) {
    3.         print("init pathfinder");
    4.         activePathFinder = new PathFinderC();  
    5.         activePathFinder.SetParent(this);
    6.         if(activePathFinder==null) print("is null..!");
    7.         print("reference: "+activePathFinder);
    8.     }
    9.  
    The above's the problem. Since it always evaluates to null, I will create tons of instances. Sure, I could use another variable as a flag and use it instead of checking for null, but I'd like to proceed in the correct way.
     
  6. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    I should mention that using something like "if (!activePathFinder)" fails as well, it always evaluates to true.

    Also, when printing the variable above, it does indeed return "PathFinderC" when created, otherwise "". Which is weird, since the same variable in the if-statement evaluates to null....
     
  7. Darko

    Darko

    Joined:
    Aug 28, 2007
    Posts:
    100
    I'm not a JavaScript expert, but maybe this will help.
    What do you want to check? If the variable exists?
     
  8. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    Yeah, or rather, if the variable has been assigned an instanced pathfinder. I only want this to happen once, so checking for null seemed to be the way to do it. The other identifiers/keywords you usually use in these circumstances (and that are mentioned on the page you linked to), void/undefined won't work. The variable isn't void even before it's assigned the instance, and undefined throws an "unknown identifier" error.
     
  9. Darko

    Darko

    Joined:
    Aug 28, 2007
    Posts:
    100
    I still do not understand clearly why you check "if null"? There is no reason why the instance should not exist after a "new" call. The only reason I can imagine is "out of memory", but this is unlikely going to happen. (and I assume that the .NET Framework throws an out-of-memory exception in this case)

    I know the habbit of C/C++ programmers to check "if null" on every "new" but in managed languages this is not needed/usual.

    But it is funny that the result really is null... :roll:
     
  10. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    The reason why I check (other than for fun ;) ) is that the same function is called repeatedly. So, if I've already created a PathFinder instance, I want to reuse it rather than create a new one.

    That approach fails if I can't reliably check whether I've already instanced the PathFinder or not.

    A related question concerns garbage collection. Let's say that I (sloppily) do instance a new one each time, can I be sure that garbage collection kicks in and kills the old one? I have no idea how garbage collection is triggered in Unity for pure code objects?

    Of course, the workaround is to set a flag when I first instance the PathFinder and check against that instead, but I want to understand how to do it properly. :)
     
  11. lgoss007

    lgoss007

    Joined:
    Dec 6, 2006
    Posts:
    88
    You get an error because the constructor you created doesn't return a value, which constructors don't. You'd simply do:
    Code (csharp):
    1.  
    2. function PathFinder() {
    3. }
    4.  
    But if you have nothing in the constructor then there is no reason to create one, as it's automatically done... As you've seen...

    Somethings not right there. You don't need to check right after it's initialized, as it should never return null if you create a new. If you run out of memory it should throw an exception before it gets to any check if it's null anyways.

    So my guess is your variable is going out of scope or is another instance. Tough to tell without seeing surrounding code, like where activePathFinder is declared, etc.
     
  12. lgoss007

    lgoss007

    Joined:
    Dec 6, 2006
    Posts:
    88
    Let me clarify my previous post, the first check for null is fine, it's the second check right after the new that's not needed...

    For the problem above this shouldn't be a concern, as that's what garbage collection is for (so the programmer doesn't have to worry about it). Checking for null is the right thing to do when you don't want to overwrite a previously created object.
     
  13. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    Something else must be wrong, as the variable is handled in the same way I usually do stuff for gameObjects, etc. which is by the book. ;)

    Here we go:
    The variable declaration is done in the javascript controlling AI for a unit, in the "header" among all the other private and public variables:
    Code (csharp):
    1.  
    2. private var activePathFinder : PathFinderC;
    3.  
    The new call and variable allocation occurs in the code snippet I already posted, which is located in a function in the same javascript. I check the state of the variable to check if it's the first time I need a pathFinder, if so create one, otherwise reuse it:

    Code (csharp):
    1.  
    2. if (activePathFinder==null) {
    3.         print("init pathfinder");
    4.         activePathFinder = new PathFinderC();  
    5.         activePathFinder.SetParent(this);
    6.         if(activePathFinder==null) print("is null..!");
    7.         print("reference: "+activePathFinder);
    8.     }
    9.  
    The result is that the PathFinder is instanced each and every time, ignoring the fact that activePathFinder has already been set to an instance of PathFinderC. It works however (calls to the instance are handled correctly) so I don't think it's a scope problem. Printing the value of activePathFinder returns "PathFinderC" as well, so it does contain a type and an instance, but it still evaluates to null in the if statement. Which is extremely weird.[/quote]
     
  14. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    Of course, I added it simply to check that I wasn't loopy. That very if statement does evaluate to true, and the print statement is executed, even though I just created a new instance and allocated it to the variable, which does contain the instance (which I check with the other print statement, it prints "PathFinderC". It's weird, it's got a value (an instance of PathFinderC), but it evaluates to null.
     
  15. lgoss007

    lgoss007

    Joined:
    Dec 6, 2006
    Posts:
    88
    Yeah... something else must be wrong. I can run some tests when I get home, but something else is wrong somewhere. I'm pretty sure that null works and a new object won't return null. Any misspellings, capitalization differences? JavaScript can do some weird things if you're not watching it carefully ;)
     
  16. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    I've checked and rechecked, but there could be an obscure error somewhere. The best way to rule it out is to do exactly what I did (ie. declare a variable, do a new SomeOnlyCodeClass(), and check if ==null evaluates to true or not. If you'd like to test it I'd be very thankful, since if it works for you I'd be most curious as to exactly how you declare your classes. That's where I'm uncertain whether I'm doing things right.

    Basically my structure is like this:
    SomeClass.js:
    Code (csharp):
    1.  
    2. class SomeClass {
    3.  
    4. var thisandthat : something;
    5. private var soAndSo : something;
    6.  
    7. function SomeClass() {
    8.  //init stuff;
    9. }
    10.  
    11. function SomeCoolFunction() {
    12.  // extremely useful stuff here
    13. }
    14.  
    15. }
    16.  
    As I wrote earlier I though my problem was that I didn't return the new instance from the constructor (something I'm sure I've done in another language, but maybe I'm just remembering incorrectly... :) ).
    Anyway, the structure above is what's used for PathFinderC, so if that's correct then I don't know what's going on.... :)

    Anyway, thanks for the help this far and thanks in advance if anyone checks the evaluation of a pure code instance in unity! :)
     
  17. Darko

    Darko

    Joined:
    Aug 28, 2007
    Posts:
    100
    I think I have the answer.

    The == operator compares the value.
    The === operator compares the value and the type.

    Example:

    x=5
    y="5"
    x==y returns true
    x===y returns false

    "null" is also an object in JavaScript. (of type "null")
    So, you have to compare with === to see if the value and the type are the same. I am not 100% sure about all that, but if I compare some_instance with === null right after a "new"-allocation, it works fine.

    http://www.w3schools.com/js/js_operators.asp
    http://lists.evolt.org/archive/Week-of-Mon-20050214/169524.html

    One more reason to switch to C#. :p
     
  18. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    Yep. That's it! :)
    === indeed works the way I expected == to. Shows how little I know about the quirks of javascript. :)

    Regarding C#, believe me I would have used it had it not been said that javascript is the "preferred" language for Unity.

    I'm still curious, are there any big drawbacks of C# in comparison to javascript in Unity development? Maybe that's been covered elsewhere a million times though. :)

    Anyway, many thanks for the help!
     
  19. Darko

    Darko

    Joined:
    Aug 28, 2007
    Posts:
    100
    Well, I am not sure if that is the cause. Imagine this:

    "some_instance" is of type "SomeObject".
    "null" is of type "xyz". (in some compiler implementations null is of type "object")

    In this case some_instance === null will also result to false.
     
  20. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    Indeed. But in my specific case it works.
    When I declare the variable, it's apparently null since (activePathFinder === null) returns true. However, as soon as I set activePathFinder = new PathFinderC() that statement no longer returns true (since as you rightly pointed out I now compare different types). Which means that I'm now able to tell if I've already created a PathFinder instance without using a separate variable as a flag. Success.

    Do I make any sense? :)
     
  21. Darko

    Darko

    Joined:
    Aug 28, 2007
    Posts:
    100
    Ah, I see. Yes, you are right. I didn't think of this simple test. :)
    Anyway, I still would like to have the confirmation of an JavaScript expert because I didn't found this solution in the www. Maybe we miss some important informations.
     
  22. Darko

    Darko

    Joined:
    Aug 28, 2007
    Posts:
    100
    Ok, I think the following is more JavaScript-like:
    Code (csharp):
    1.  somevar = new SomeClass();
    2.  if(somevar instanceof SomeClass) {
    3.     print("is instance!");
    4.  } else {
    5.     print("is not instance!");
    6.  }
    I don't know for sure why the other attempt also works but who cares... :D
     
  23. lgoss007

    lgoss007

    Joined:
    Dec 6, 2006
    Posts:
    88
    This seems like something specific to Unity (maybe Mono)?. Both the == and === operator work in other implementations of JavaScript using something like:
    Code (csharp):
    1.  
    2. var x;
    3. if(x == null) // true for == and ===
    4. {
    5.    x = new String("Hello");
    6.  
    7.    // false for == and === (but true for == in Unity?)
    8.    if(x == null) ...
    9. }
    10.  
    Yes == and === are different, but I believe both should work in this situation (but obviously they don't). However Unity JavaScript is different, so only the Unity Devs can say for sure how it's supposed to act, and why it acts like it does...

    Another article here:
    http://rayfd.wordpress.com/2007/03/18/really-understanding-javascripts-equality-and-identity/
     
  24. Darko

    Darko

    Joined:
    Aug 28, 2007
    Posts:
    100
    Ok, but do the Unity developer read this part of the forum? Or should we post it elsewhere? I am very new here and don't know anything about the Unity bug tracking processes.
     
  25. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    The Unity team reads the forum, but only answer some questions - presumably the most important ones. :) I think this one may be under their radar.

    Anyway, if we're sure it's a bug I'll submit it. I assume you've tested problem, so it's not just me being daft? :)
     
  26. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    In the editor Unity automatically creates wrapper objects for null references.
    This is how we can give detailed exceptions instead of just NullReference exception eg.

    Missing Reference Exception: The object "My object" is missing on the game object "zulu" most likely you forgot to assign a reference in the inspector.

    The == operator takes this into account but the === operator checks for existance thus will always return true in the editor but not in the player.
     
  27. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    So the behaviour in the player is different? Sorry for the ignorance, but given the above, what's the right way to check whether a variable has been assigned an instance or not?

    I'm using "===" at the moment, and it's not always returning true (in the editor).
    Code (csharp):
    1.  
    2. someVarieble===null
    3.  
    appears to return true only if the variable is truly null (not assigned an instance in my case). Are you saying this changes when I run a build?

    [edit for clarity : note that I'm speaking of non-gameobject instances - pure javascript instances]
     
  28. lgoss007

    lgoss007

    Joined:
    Dec 6, 2006
    Posts:
    88
    But in this case == always returns true in the player, so is this a bug or by design? For example (attach this to an empty game object):

    Code (csharp):
    1.  
    2. private var test : String = null;
    3.  
    4. function Update {
    5.    if(test == null) {
    6.       Debug.Log("null")
    7.       test = new String("hello");
    8.    }
    9. }
    10.  
    This will continuously print null to the debug log instead of just printing it once. So is this a side effect of handling the wrappers? If so that's another JavaScript difference.

    (I have a list of JavaScript differences here):
    http://www.unifycommunity.com/wiki/index.php?title=Programming_Introduction
     
  29. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    You should NOT use === use == instead.
    === is something you use very very rarely. I can't think of a single case where you would want to use it in game code.
     
  30. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    The example script you posted lgoss (After fixing the compile errors in it) works fine. As in, prints null one time.
     
  31. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    OK, then we're back to square ONE. This is what I'm doing:
    Code (csharp):
    1.  
    2. private var activePathFinder : PathFinderC = null;
    3.  
    <snip unrelated>
    Code (csharp):
    1.  
    2. if (activePathFinder==null) {
    3.         print("init pathfinder");
    4.         activePathFinder = new PathFinderC();  
    5.         activePathFinder.SetParent(this);
    6.         if(activePathFinder==null) print("is null..!");
    7.         print("reference: "+activePathFinder);
    8.     }
    9.  
    The above code is executed frequently. If I use "==" as above, the if clause evaluates to true each and every time as described above. And "is null...!" prints as well. If I use "===", it evaluates once, like it should, and "is null..!" doesn't print.

    What am I doing wrong?
     
  32. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    BTW, I'm not sure that example script is a valid comparison. String isn't a user defined class, whereas I'm instancing a class of my own.
    Either that makes a difference internally when doing a == comparison with null, or we're back to my original question which was how to properly define a class with a constructor which takes care of the problem.

    Granted, I can easily work around the problem this time, but I'd like to know how to do it properly. :)
     
  33. lgoss007

    lgoss007

    Joined:
    Dec 6, 2006
    Posts:
    88
    Sorry about the errors (semicolon?), don't have Unity with me at the moment. But I know I've got it to print multiple times with a custom object after dfvdan told me about his problem and I duplicated it. I'll try to remember what the code was. dfvdan can you check out the scripts below and confirm so I don't keep wasting Joachim's time :wink: (if this doesn't print null over and over I'll have to wait till I get home).

    Code (csharp):
    1.  
    2. // File: MyObj.js
    3.  
    4. // Object or GameObject... don't remember which I did
    5. private var parent : Object;
    6.  
    7. function SetParent(p : Object) {
    8.    parent = p;
    9. }
    10.  
    Attach this to empty game object.
    Code (csharp):
    1.  
    2. // File: MyObjTester.js
    3. private var test : MyObj = null;
    4.  
    5. function Update {
    6.    if(test == null) {
    7.       Debug.Log("null");
    8.       test = new MyObj();
    9.       test.SetParent(this);
    10.    }
    11. }
    12.  
     
  34. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Now i see. Well the thing is what you are creating is an component. A component can't exist in void, so what you really want to use is AddComponent. Otherwise to what would the component be attached?

    If you want to create a class that does not inherit from component thus isn't attached to a game object do like this:

    class MyClass
    {

    }

    By default, if you just create a script it will implicitly inherit from component and monobehaviour in javascript.
     
  35. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    Alright, I've created a new scene and tested it just to make sure. Unless I'm an absolute fool (in which case I'll buy Joachim a beer when I see him in the future), the null test fails.

    My test class:
    Code (csharp):
    1.  
    2. //file TestClass.js
    3.  
    4. class TestClass {
    5.  
    6.     private var myParent : TestScript;
    7.  
    8.     function TestClass() {
    9.            
    10.     }
    11.    
    12.     function SetParent(tparent : TestScript) {
    13.         myParent = tparent;
    14.         print("parent set!");  
    15.     }
    16.    
    17. }
    18.  
    And a new scene with an empty gameobject with the following script attached:
    Code (csharp):
    1.  
    2. private var testObject : TestClass = null;
    3.  
    4. function Update () {
    5.     if (testObject == null) {
    6.         print("is null..!");
    7.         testObject = new TestClass();
    8.         testObject.SetParent(this);
    9.         if (testObject == null) print("is STILL null! :P");    
    10.     }
    11. }
    12.  
    All print statements print, to infinity and beeeyond! ;)
     
  36. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    [edit]

    Sorry, just edited my reply since I missed something in the code you referenced. Check my test instead, I did do (what I think is) a proper class declaration, and it still behaves in a way I don't think is expected...?
     
  37. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Change this:
    class TestClass {

    to this:
    class TestClass extends System.Object {

    This particular issue is fixed in 2.0. So in 2.0 both will work.
     
  38. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    Excellent. Many thanks, Joachim! :)
     
  39. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    Sorry, spoke too soon.
    After adding "extends System.Object", I get "Unknown identifier : Instantiate" in my real class. I can guess why it's happening, and I think it means the solution is called 2.0?
     
  40. Darko

    Darko

    Joined:
    Aug 28, 2007
    Posts:
    100
    Do you have seen this?

    @Joachim
    What do you think about using the instanceof operator in this case?
     
  41. Foxxis

    Foxxis

    Joined:
    Jun 27, 2006
    Posts:
    1,108
    Yep, sorry I didn't reply. I haven't tried it, as I worked around the problem in a more elegant way. I just moved the instancing to a more logical place.
    So the discussion was more of the theoretical kind. I can imagine cases where a test like that is important, so it's good to know how to test for null / not instanced in a proper way.

    I'll give it a whirl in my testclass/script anyway to see how it behaves. :)

    Thanks to everyone who participated, much appreciated!