Null Object Access
Monkey Forums/Monkey Programming/Null Object Access
| ||
I have been chasing a "Null Object Access" error in a large project for a couple of hours now, and seem to found the problem. Here is a small piece of code to show the issue: Strict Import mojo '------------------- Global mainClass:MainClass ' Main() Function Main:Int() mainClass = New MainClass Return True End '------------------- ' MainClass Class MainClass Extends App Field image:Image Field class1:Class1 Method OnCreate:Int() Self.image = LoadImage("icon.png", 1, Image.MidHandle) Self.class1 = New Class1 SetUpdateRate(60) Return True End Method OnUpdate:Int() Self.class1.Update() Return True End Method Method OnRender:Int() Cls class1.Draw() Return True End Method End '--------- 'Class1 Class Class1 Field baseImage:Image Field class2:Class2 Method New() Self.baseImage = LoadImage("icon.png", 1, Image.MidHandle) Self.class2 = New Class2 End Method Method Update:Int() ' If KeyHit(KEY_SPACE) ' Self.class2 = New Class2 ' EndIf Return True End Method Method Draw:Int() Self.class2.Draw() ' If class2 <> Null ' Self.class2.Draw() ' EndIf Return True End Method End Class '------- ' Class2 Class Class2 Field image:Image Method New() Self.image = mainClass.class1.baseImage End Method Method Update:Int() Return True End Method Method Draw:Int() DrawImage Self.image, 200, 200 Return True End Method End Class ' ---------- .. and HERE is the image used in the above code. Running that code "as-is" gives me the "Null Object Access" error. However if you swap around the Draw call in Class1 and comment out the existing Draw, and do the same for the New Class2 lines (in OnUpdate() and New() of Class1) then the code works as expected. So, it seems as though the New() method (Class1) has to complete (get to the End Method) before Self.baseImage becomes a valid reference to the image loaded in Class1. Am I correct in thinking this or have I missed something obvious? Also is there a better way to access the Fields of other classes than the way I am doing it? ie. "mainClass.class1.baseImage" I am trying to load a single image and re-use it from multiple classes, rather than loading multiple images in the New() method every time I create a new instance of a class. Thanks. PS. Just to add, the error reported by Monkey is line 94, which is in Class2's New() method at: Self.image = mainClass.class1.baseImagewhich is called before the "New Class2" call so should be loaded and thus a valid image? Sorry for the ramble. |
| ||
In my opinion, the class1 and class2 fiels looks terribly worng in therms of OO design. If a member of a class has to be shared in class instances, it sould not be a field, it should be a global. How would it look to you to remove the class1 and class2 fields, and just make the image field a global and, just in case it is null on object creation, call the LoadImage command? EDIT: That said the error is caused by your Main function. This sencente: mainClass = New MainClass Is causing the error. When this is called, a cascade of New(s) happen BEFORE the result of the xpression New MainClass is given to the mainClass global. But the mainClass global (wich is null while the New MainClass is being executed) is trying to be used by the New Class2 (to set an imagE). |
| ||
Hey ziggy, thanks for the reply. That makes sense after reading your reply, however after some more experimenting, the following code works: Strict Import mojo '------------------- Global mainClass:MainClass ' Main() Function Main:Int() mainClass = New MainClass Return True End '------------------- ' mainClass Class MainClass Extends App Field baseImage:Image Field class1:Class1 ' ================================= Method OnCreate:Int() Self.baseImage = LoadImage("icon.png", 1, Image.MidHandle) Self.class1 = New Class1 SetUpdateRate(60) Return True End ' ================================= Method OnUpdate:Int() Return True End Method ' ================================= Method OnRender:Int() Cls Self.class1.Draw() Return True End Method ' ================================= End ' ------------------------- ' class1 Class Class1 Field image:Image Method New() Self.image = mainClass.baseImage End Method Update:Void() End Method Draw:Void() DrawImage Self.image, 200,200 End EndThe image is loaded, then a New() Class1 is created - within which I set the image to point to the image loaded in mainClass. After your explanation above, this code should also give a Null Object Access as it is doing the exact same thing (trying to access mainClass.baseImage when mainClass is null as it is still being executed) - but it works fine and draws the image. The only difference is I am using one less class in this example, and calling the LoadImage in mainClass - but the "mainClass = New mainClass" call is still executing like the previous example (and as you say should be null) so should give the same error...? If I move the "Self.class1 = New Class1" before the LoadImage (in mainClass) then I get a Null Object Access as expected as the image has not been loaded yet. I am obviously misunderstand something, but I'm not sure what I am missing. Thanks. |
| ||
I have done something like this in cases where a window class handles loading graphics, and passes its choice of graphics to buttons etc. Maybe something like that is what the OP is really planning to do. However I have moved more towards globals myself in this case, although rarely 'naked' globals. I have a globals.monkey source file that contains managers for commonly used images, fonts, sounds etc., so classes that want a "Help" button can call the global function GetButtonIcon:Image( BUTICON_HELP ). |
| ||
In your first example you try to use the instance Field class1:Class1 in a moment, when it it not yet created. The field "class1:Class1" will be created, when returned from this New(): Class MainClass Extends App ... Method OnCreate:Int() Self.class1 = New Class1 but this New() first jumps down to other classes to other New()s. during this process inside the last New()... Class Class2 ... Method New() Self.image = mainClass.class1.baseImage End Method you call the reference, that is not finally created at this moment. I know cases, where I also need a construction like this, but I do it this way: Class MyGame field Sub:Class1, img:Image OnCreate() img=LoadImage(....) sub=New Class1 sub.StartUp End end Class Class1 field sub:Class2 Method New() Self.sub = New Class2 End Method StartUp() Self.sub.StartUp End end Class Class2 field img:Image Method StartUp() Self.img = MyGame.img End end Another proposal without "any main class image": Class MyGame field sub:Class1 OnCreate() Local img = LoadImage(....) sub = New Class1 sub.StartUp(img) End End Class Class1 field sub:Class2 Method New() sub = New Class2 End Method StartUp(loc:Image) sub.StartUp loc End end Class Class2 field img:Image Method StartUp(loc:Image) img = loc End End |
| ||
Thanks for the replies guys. @Gerry No it's nothing to do with a GUI - I basically have a framework (app) which within that will run different minigames - I am using iEngine from the Ignition Framework to change states between main app <-> minigames. As such the minigames will be run, and then nulled when complete (and back to the main app) so I don't really want to use Globals as from the Monkey docs it says they are "permanent", and I don't want a load of images hanging around in memory when they might not even be used. My OOP is getting better all the time, but I am far from an expert! Have I misunderstood the docs in regards to Globals? @Midimaster Thanks for the examples :) I actually refactored the code last night and ended up going the same route of your second example, passing the image reference into the New() method of the class, and this worked great. I need to rewrite it this week though as it got a bit messy due to the lateness and my brain slowly going to sleep. @All I am still confused as to why my first example fails and the second example works, even though they seem (to me) to be doing the exact same thing in terms of the image assignment? |
| ||
No, you are right, although another option to consider might be to make the mini-games completely self-contained and let each load its own graphics. You might still like globals for elements common to all or most games, e.g. fonts. Obviously a global image manager could discard images too if it needs to, and load then on demand rather than at the start. But mostly I think we are talking about elements of organisation here, so do it the way that makes most sense to you. |
| ||
Hey Gerry, I was/am trying to make the minigames self-contained and load it's own graphics, sorry if my previous posts confused matters. The minigame did in fact work perfectly when I had it running as it's own singleton, this problem happened when I dropped it into my main game, although this is the 5th game I have added and it's the first time this issue has appeared. I do also use Globals for fonts as like you say they are used throughout the entire game + minigames + menus etc so that makes complete sense, but minigames will be loaded and nulled as needed so I can't really use Globals in that case. The problem I am having is coming from trying to access the graphics loaded in the minigames main class from other classes to do with that minigame. I'll try to use pacman as an example to try and show what I mean with a cutdown piece of code... ' I have a Field to hold the PacmanGame from my main app where I create it and ' use Ignitions iEngine to change state to it. ' minigamePacman = New PacmanGame ' iStart(minigamePacman,60) Class PacmanGame Field player:Player Field dots:Dot Field dotImage:Image Method New() Self.player = New Player Self.dotImage = LoadImage("dot.png", 1, Image.MidHandle) Self.dots = New Dot End Method End Class Class Player Field image:Image Method New() ' I can load the image here as there is only ever 1 player object Self.image = LoadImage("player.png", 1, Image.MidHandle) End Method End Class Class Dot Field image:Image Method New() ' I try to load each dot using the image loaded in PacmanGame ' as I do not want to load a new image every time I create a ' New() dot object - as there will be loads. Self.image = minigamePacman.dotImage End Method End Class The above code would give the same Null Object Access as my OP as I am doing the same thing, and minigamePacman would = null while the New() method was still being run. As per my post above I can fix this by passing the image as a parameter into the New() method of the Dot class, but from your comment on keeping everything "self contained" I am thinking there is a better way to do this or something I am completely missing, or whether passing it like that is normal? I'm also unsure if it is correct/safe to be accessing Class fields from other Classes in the way I am doing: mainApp.myClass.myObject.myField - although I seem to remember a post somewhere on here mentioning this method so assumed it was OK to use. Am I misunderstanding OOP or how I should be doing things? It's more confusing also as my second example in this thread worked fine despite it doing the same as the first example in my OP, so I am a bit puzzled by it all at the minute. Thanks. |
| ||
you are doing always the same mistake.... and it is always the same problem of timing: In Class Dot you use the object "minigamePacman" during the New() Method. But in this moment this object "minigamePacman" is not created!!! It will be created when (after) all New() methods have returned back to the Main() function. In your code all New() methods happen during the first New() and not after it. A New() creates an object, but the reference is started first in the next line after calling the New(): .... minigamePacman = New PacmanGame ' in this moment "minigamePacman" is not alive!!! ' now the next line: mingame.x=4 ' in this moment "minigamePacman" is alive!!! .... Class PacmanGame Method New() blabla ' in this moment "minigamePacman" is not alive!!! End ' in this moment it starts to live!!! End This will happen step by step: .... step: command: ------------------- 1. ----> minigamePacman = New PacmanGame ' in this moment "minigamePacman" is not alive!!! 2. --------> Method New() 3. --------> blabla ' in this moment "minigamePacman" is not alive!!! 4. --------> End ' in this moment it starts to live!!! 5. ----> now the next line: 6. ----> mingame.x=4 ' in this moment "minigamePacman" is alive!!! ... What you are trying to do is to refer to "minigamePacman" somewhere between step 2. and 4. but you first can use it after step 5. |
| ||
Midimaster, yeah I know this from the first replies, I even said so myself in my last post... The above code would give the same Null Object Access as my OP as I am doing the same thing, and minigamePacman would = null while the New() method was still being run. My pacman code was to try and clearly emphasise why I was trying to do what I was doing with some code - it was written for this thread :) Could you please explain why my second example in this thread (post #3) works fine even though it is doing the same thing as the code in the OP and the pacman example in terms of the New() method not being finished as you clearly described. Cheers. |
| ||
If I understand your examples right, I think the difference is that in the second example you are loading the image during OnCreate() instead of New(). When you call OnCreate() the app is already constructed and has a proper address, so you can load an image at one point in the OnCreate() function and access it at a later point. You could probably set up your MiniGame class so it has a separate initialisation function that you call after it is constructed, similar to App.OnCreate(). |
| ||
that is easy to explain... the New() of mainClass:MainClass is already finished when OnCreate() is called. Oncreate() is not the same like New() As I did understand it OnCreate() is a event driven call caused by the operation system and is not the same like a New(). steps: 1. ---> Main() 2. ------> Function Main:Int() 3. ------> mainClass = New MainClass 4. ------> Return True ' from this moment object "mainClass" lives! 5. ------> End 6. ---> OS 7. ---> Create() 8. ------> Method OnCreate() 9. ------> Self.baseImage = LoadImage("icon.png", 1, Image.MidHandle) 10. ------> Self.class1 = New Class1 11. ---------> Method New() 12. ---------> Self.image = mainClass.baseImage ' no problem because object "mainClass" already lives! You can say, that OnCreate() is something like my StartUp(). It is a second call of object "mainClass" after a finished New() |
| ||
Ahhhhhhhh I didn't realise that, I thought New() was essentially OnCreate() for our own classes outside the main App class - that makes sense now. One last thing if you would be so kind, as I would like to make sure I am doing things correctly so as not to cause bigger issues down the line, I'm still learning the OOP ways! Which is the preferred/accepted/better method in terms of OOP for doing what I am trying to do from the methods posted? ie. Passing the image as a parameter to New() or usig a seperate initialisiation function to load them after New() has completed, or are they both fine depending on what the coder prefers? Also are there any issues with accessing things from other classes using the mainApp.myClass.myObject.myField way (where mainApp is a Global handle to my main app instance) So if I have 3 classes inside each other (similar to the examples posted), and I want to access a Field from an object in Class2 whilst in Class3 I would do something like: ' mainApp = Global handle to mainApp instance ' myClass1 = Field inside mainApp ' myClass2 = Field inside Class1 ' myObject = Field Inside Class2 ' myField = Field inside myObject (which is a class ie. player/enemy etc) mainApp.myClass1.myClass2.myObject.myField Thanks so much for your help! |
| ||
to be honest.. I don't know! At the beginning of my "monkey time" I was very careful and did'nt allow me a lot of things, which are technical possible. Nowadays I code more radical with wild constructions and all variants, that are possible. I think, as long as you do not have performance problems you need not think about the best of 10 ways. Just do it. As long as you do not work with thousands of objects but only some dozent, you will not get any performance problems. And if you get performnce problems you will have to start to examine all 10 ways to find out, what is the best on which target. for mother-related operations like your example "accesing a field in class 2 whilst in Class 3" I would do it this way: Class2 Field X%, Y%, bla:Class3 Method blabla() .... bla:Class3= New Class3 Class3.Mother=Self ..... End [code]Class3 Field Mother:Class2 Method Draw() .... DrawText "hello", Mother.X, Mother.Y End End |
| ||
The purpose of OO is to help you organise your code, keep things together that should be together, and keep things separate that should be separate. If it gets in the way, remember YOU are the boss. [The machines will take over soon enough, so enjoy it while you can, puny human.] Organise things the way that makes sense to you. Flexible OO languages like Monkey (and most of its targets) support a lot of ways of doing things, so if the language is getting in the way, there is probably a way of doing what you want that can be expressed nicely in the language too. All the things you want to do are fine in context except you are scattering image references around between classes a little more promiscuously than I would like. For example, I would never give a dot in Pacman its own image, unless it's a super-dot that has all sorts of individual animations. I would have a game window class that draws all the dots. That said, you are using an engine and maybe that is pushing you in certain ways. And you can, of course, have the dot draw itself if that's what you want anyway. |
| ||
Thanks for the info and advice both, it's really appreciated. As I've said I am still finding my way with OOP, and I'm usually the kind of coder who if it works I leave it and move on, but I'm not sure that is the best way to go with OOP as there may be consequences down the road. I'm not using a framework, all my code is my own apart from the state machine from Ignition which I use to move between self contained parts of my game (Start menu, main game, mini games etc), but other than than its all my own blood sweat and tears lol. I decided against using Ignition as being new to Monkey I wanted to make sure I knew what was going on under the hood and not have things hidden away behind behind the framework with no real idea of what was really happening. I don''t regret the choice at all, and although it has been hard work writing it from the ground up means I know everything inside out, but it may not necessarily be the correct way to do things.- hence this thread and my questions. :) |