What is the point of extending types?

BlitzMax Forums/BlitzMax Beginners Area/What is the point of extending types?

sswift(Posted 2006) [#1]
If you can't even extend one which is somewhat complicated?

I am trying to figure out how to make use of extended types to make my code neater and I figured it wouldn't work the way I hoped it would and lo and behold it won't compile.

Here's my types, simplified:

Type Sprite
    Function Create:Sprite(Image:TImage)
        Local NS:Sprite 
        NS = New Sprite
        Return NS
    End Function
End Type

Type Fruit Extends Sprite
    Field ID%
End Type


If I try to do this:

Local ThisFruit:Fruit
ThisFruit = Fruit.Create(Image)

I get: "Unable to convert from Sprite to Fruit."

How am I supposed to do this without creating a whole new Create function in the Fruit type that does all the same initialization?


FlameDuck(Posted 2006) [#2]
How am I supposed to do this without creating a whole new Create function in the Fruit type that does all the same initialization?
Downcast it. Like so: ThisFruit = Fruit(Fruit.Create(Image)).

But that's not really the main point of extending types, the main point is polymorphism - the elimination of convoluted spagetti-like nested if and select case statements, by letting the compiler sort it all out.


N(Posted 2006) [#3]
How am I supposed to do this without creating a whole new Create function in the Fruit type that does all the same initialization?

You can't. You can't do this even in C++, so I can't see why you would expect to be able to do this in BlitzMax. Frankly, it's simple logic.

All fruits are sprites. This does not mean that all sprites are fruits. Your function does not create a fruit, it creates a sprite. Let's look at your function if you expand it: Local f:Fruit = New Sprite. What you are doing here is creating a sprite and assigning it to a variable that expects a fruit.

This should make sense to you. If it doesn't then you should probably give up on OOP right here and now.


Nelvin(Posted 2006) [#4]
I guess he has probably only fallen into the trap of BlitMax not supporting a more than basic way for object creation and though the sample is a bit missleading.

BMax does not support constructors with arguments (like C++ and all other OOP languages do) and though you have to implement workarounds by implementing constructors as a factory/create function for each type and find a way to share the init code of parenttypes using another function and remember for each new derived type to chain the init call.


Grey Alien(Posted 2006) [#5]
in my framework I made a TScreen type with some basic methods defining default behaviour. Then I extend this into other screens e.g. TitleScreen, GameScreen, OptionsScreen etc. I keep a pointer called CurrentScreen:TScreen and then I am able to say CurrentScreen.Draw() and it will either call the default draw method in TScreen OR an overridden version in whatever the currentscreen is (e.g. TitleSreen). Also within the overridden methods I often call Super.Draw() for example to do the default drawing and then I add to it. This is polymorphism at it's best.

Sswift: you are still welcome to take me up on that offer and then you can see how I did it, if you are interested.


skn3(Posted 2006) [#6]
Imagine in your example, you can have your sprite type which will handle all rendering, and contain all fields.

Then with each extended type from the sprite, you have an update method. So an apple could update by turning, and updating the fields (in the parent type). An orange could update by enlarging... and so on.

In your main loop, you could simply have a for next that will iterate all sprites and call update, and then render. Regardless if it is an extended apple, orange, pineapple, cherry or lemon the system would work.


TomToad(Posted 2006) [#7]
Try reading this excellent tutorial on Types and OOP.
http://www.blitzbasic.com/Community/posts.php?topic=59233


sswift(Posted 2006) [#8]

BMax does not support constructors with arguments (like C++ and all other OOP languages do) and though you have to implement workarounds by implementing constructors as a factory/create function for each type and find a way to share the init code of parenttypes using another function and remember for each new derived type to chain the init call.



So what I'm trying to do ISN'T crazy then.

But what I don't get is there's not just this one constructor which is an issue because of the type it returns. Other sprite functions TAKE sprites as parameters. For example: ThisSprite.Distance#(OtherSprite)

And still others use a Sprite() tpyecast within then to convert the value stored in the linked list of sprites to a sprite.

If I can't extend this sprite type because of these issues, then either there must be a way to work around it that is simple, or this whole extend thing is retarded.

[quite]All fruits are sprites. This does not mean that all sprites are fruits.[/quote]

That's true. So I should be able to do ThisFruit.Distance#(OtherFruit) even though the function takes a sprite as a parameter, right?

I asked for a sprite, and you give me a fruit, which is a type of sprite.

That doesn't explain why Fruit = Sprite doesn't work though. Conceptually it's a bit hard to wrap your head around, but programmatically, it would just take all the properties of sprite and stick them in the fruit, which makes perfect sense because fruit has all the necessessary variables being an extension of sprite.

On the other hand, the logical inverse of that is Sprite = Fruit. And that clearly will not work from a programming point of view, at least as far as not losing a bunch of data in the process.

ONE of these should logically exist, since "a fruit is a sprite", and since the first one makes the most sense, and doesn't result in a loss of data, that seems like the logical choice.

The only problem with Fruit = Fruit.Create() that I can see is that in the function it says New Sprite not New Fruit. I can't really see a way around that issue. It's not creating a new Fruit, so even if Fruit contained a link to the fields that were part of the sprite type now your fruit type would only have half the memory allocated that it needs.

I can accept that that doesn't work, but what I need to know is what will happen if I do this:

	' -------------------------------------------------------------------------------------------------------------------------------------------------------
	' This function creates a new sprite, and returns a pointer to it.
	'
	'	Image  : The image the sprite will use.  May be null, if, for example, you wish to simply create an invisible parent for other sprites.
	'	Order  : The order the sprite will be drawn in.  Order can be positive or negative.  Sprites with a lower order are drawn first.
	'			 If two sprites have the same order, they will be drawn in the order in which they were created.	
	'	Parent : The parent of this sprite.  A sprite does not need to have a parent, but if it does, it will be positioned relative to it.
	'
	' The sprite's blending mode, scale, color, alpha, and viewport will be set according to the current values you have set with the Max2D functions.
	' This is so that you can set the blending mode once for example, and all sprites you create from then on will use the direcred blending mode.
	'
	' Example:
	'
	' 	ThisSprite:Sprite = Sprite.Create(Image)
	' -------------------------------------------------------------------------------------------------------------------------------------------------------

		Function Create:Sprite(Image:TImage=Null, Order%=0, Parent:Sprite=Null)
	
			Local NS:Sprite
			
			NS = New Sprite
						
			NS.SetParent(Parent)							
			NS.SetImage(Image)
			NS.SetOrder(Order)
					
			If Image <> Null Then NS.SetRadius(Min(ImageWidth(Image), ImageHeight(Image))/2)
					
			Return NS
	
		End Function	


	' -------------------------------------------------------------------------------------------------------------------------------------------------------
	' This method is called when you create a sprite via SpriteName = New Sprite.
	' It allows you to create types that extend the sprite type.  Ie: Type Fruit Extends Sprite.
	'
	' If you create a sprite using New Sprite, then you don't need to call the Create() function.  The sprite will however default to having no image, an
	' order of 0, no parent, and a radius of 0.  You will need to at least set the sprite's image after creating it if you want it to be seen.
	' -------------------------------------------------------------------------------------------------------------------------------------------------------
		
		Method New()

			Local ScaleX#, ScaleY#
			Local R%, G%, B%
			Local VPx%, VPy%, VPw%, VPh%
						
			' Add sprite to the end of the sprite list, and save a pointer to the link created.
				Self._Link = SpriteList.AddLast(Self)
		
			' Config sprite.
			
				Self.SetBlend(.GetBlend())
				
				.GetScale(ScaleX#, ScaleY#)
				Self.SetScale(ScaleX#, ScaleY#)
				
				.GetColor(R, G, B) 
				Self.SetColor(R, G, B)
				Self.SetAlpha(.GetAlpha())
						
				.GetViewport(VPx, VPy, VPw, VPh)
				Self.SetViewport(VPx, VPy, VPw, VPh)
				
				Self.SetMass(1)
				Self.SetElasticity(1)
		
		End Method


Now in theory with this setup, I should be able to go: Fruit = New Fruit and get a fruit right? The New method in my sprite type will be called, won't it?

But what happens with that list I have there? All the rest of my sprite functions like Sprite.Draw() assume that all of those objects in that list are sprites.

So I do: For ThisSprite = EachIn SpriteList

Will that work when the object that was inserted into the list was actaully a fruit, which is technically also a sprite, but it's type is fruit, and ThisSprite is of type Sprite?

I'm so confused.

And yes, it would be nice to be able to be able to avoid these Create functions by being able to pass parameters with New.


sswift(Posted 2006) [#9]
Well I got my game to compile by changing:

ThisFruit = Fruit.Create(Image)
to
ThisFruit = New Fruit
ThisFruit.SetImage(Image)

But the only sprite that is drawn now is the background. The function to draw all the sprites will not work on fruit that were added to the sprite list. Ugh!

	Function DrawAll()		
			
			Local ThisSprite:Sprite						
			' Sort sprites from near to far.
				SpriteList.Sort()
				
			' Draw sprites.
				For ThisSprite = EachIn SpriteList
					ThisSprite.Draw()
				Next
			
		End Function			


That's the function that is called in the sprite type.

This Extends thing seems more and more retarded. If Fruit is a Sprite, then why can't I loop through all the sprites in my sprite list (some of which are fruit) and draw them?


Gabriel(Posted 2006) [#10]
But what I don't get is there's not just this one constructor which is an issue because of the type it returns. Other sprite functions TAKE sprites as parameters. For example: ThisSprite.Distance#(OtherSprite)


Yes, but all fruits ARE sprites so if you implement this method for sprites, all fruits will be capable of using the distance method, and it will work whether the sprite is a fruit, a sprite or any other type derived from sprites.


assari(Posted 2006) [#11]
Sswift,
Take a look at how I do it in my upcoming tutorial here


sswift(Posted 2006) [#12]
assari:
I don't know what you want me to see there, but all I want to know right now is why my sprites aren't drawing with the code I just posted.


Dreamora(Posted 2006) [#13]
sswift:

if you extend them all from sprite, they will be in the list.
The inherit all the functionality from sprite as well but use their overwriten methods if they have one.

So if you go through the list with t:Sprite = eachin spritelist and call t.draw then it would call draw on the appropriate class (either on sprite if it is a sprite or on fruit if it was a fruit that was casted to sprite).

This is because a fruit is a sprite as well but even more.


sswift(Posted 2006) [#14]
Dream:
Well it sounds like you're saying it should work. But it doesn't.


assari(Posted 2006) [#15]
Sswift,
The variable in the for..each/in iterator has an impact on the types that pops out of the list.
Consider this code
Strict

Type item
	Field price:Int
	
	Method New()
		self.price=5.0
	EndMethod
	
EndType

Local MyList:TList=CreateList()

Type Fruits Extends item

	Field name:String
	Field price:Float
	
End Type

Type IceCream Extends item

	Field price:Float
	
EndType

Local apple:Fruits=New Fruits
apple.price=10.0
ListAddLast MyList,Apple
Local USD:Icecream=New IceCream
ListAddFirst MyList,USD
Local xxx:Fruits=New Fruits
ListAddLast MyList,xxx

Print "Fruit Items"
For Local i:Fruits=EachIn MyList
  Print i.price
Next

Print "All Items"
For Local i:item=EachIn MyList
  Print i.price
Next

Print "Total items in list="+CountList(MyList)



sswift(Posted 2006) [#16]
I got it working. I failed to increment the version number on my sprite system include, so the New method I made did not exist, but it didn't fail because the default New method existed, and thus important variables didn't get initialized when I created sprites with New.

But I'm having a new problem. No pun intended!

	Type Fruit Extends Sprite
	
		Global _List:TList = CreateList()
	
		Field ID%
		
		Method New()
			Super.New()
			_List.AddLast(Self)
		End Method
		
	End Type


I get the error "Expecting expression but encountered )" on the line Super.New().

My intent there is to run the sprite New() method which is run normally if the fruit's new method isn't there to override it, but it doesn't work. Why?


sswift(Posted 2006) [#17]
Hm... It would appear Super.New() isn't even necessary, because BOTH the SPRITE'S New() method, and the FRUIT'S New() method are being executed!

I know this is the case because the sprites are being drawn, which means they are in the sprite list, which means the Sprite's New() method was executed, and I know the fruit are in the fruit list, because I did a CountList() and there are 60 of them, and they are only added to that list if the FRUIT'S New() method is executed.

This is great and all, but doesn't this go completely against the notion that a type's methods will OVERRIDE the methods of the super type? The sprite's New() method isn't being overriden here at all, it's being executed right along with the fruit's New() method. I'm not sure which is being executed first... Though I would hope it was the Sprite's.

Can someone explain this apparent inconsistency?

(One wonders if the Delete() method would have the same behavior, assuming one could count on it being called, which one cannot.)


skn3(Posted 2006) [#18]
does this help ?

Type sprite
	Field x
	Field y
	Field name:String
	
	Method update() Abstract
End Type

Type apple Extends sprite
	Function Create:sprite(name:String)
		Local a:apple = New apple
		a.name = name
		Return a
	End Function
	
	Method update()
		x:+1
	End Method
End Type

Type lemon Extends sprite
	Function Create:sprite(name:String)
		Local a:lemon = New lemon
		a.name = name
		Return a
	End Function
	
	Method update()
		y:+1
	End Method
End Type



'to demonstrate the different update functions, apples will 
'move on the x dimension And lemons will move on y dimension
Local s1:sprite = apple.Create("red   apple")
Local s2:sprite = lemon.Create("large lemon")
Local s3:sprite = apple.Create("green apple")
Local s4:sprite = lemon.Create("small lemon")

Local list:TList = CreateList()
list.addlast(s1)
list.addlast(s2)
list.addlast(s3)
list.addlast(s4)

Local s:sprite
'before update
Print ""
Print " * before update *"
For s = EachIn list
	Print "sprite("+s.name+") x("+s.x+") y("+s.y+")"
Next
Print ""

'update all sprites
For s = EachIn list
	s.update()
Next

'after update
Print " * after update *"
For s = EachIn list
	Print "sprite("+s.name+") x("+s.x+") y("+s.y+")"
Next

WaitKey()



N(Posted 2006) [#19]
Can someone explain this apparent inconsistency?

This is how it works in C++ as well. There's nothing inconsistent about it. In order to create a fruit you have to create the sprite as well, and as such the New method for a sprite must be called.


sswift(Posted 2006) [#20]
So really, "Type Fruit Extends Sprite" is structured like this internally:
Type Fruit
   Field Sprite:Sprite
End Type

With the obvious exception that you do not need to specify the name of the field to access the sprite's fields functions and methods.

Well... as long as it's not a bug. Does this mean the delete method of the sprite will also be called as I suspected?


N(Posted 2006) [#21]
Yes.


Dreamora(Posted 2006) [#22]
If it is called at all, then yes.

New and Delete are default methods that are called up the inheritance tree.


sswift(Posted 2006) [#23]
What if I do this?

	Type Fruit Extends Sprite
	
		Global _List:TList = CreateList()
	
		Field _Link:TLink
                Field ID%
		
		Method New()
			_List.AddLast(Self)
		End Method


		Method Free()
			
			' Remove fruit's link from list. 
				RemoveLink _Link
				_Link = Null
			
		End Method
		
	End Type



And my sprite systme has this method to free a sprite:

		Method Free()
			
			' Remove sprite's link from sprite list. 
				RemoveLink _Link
				_Link = Null
			
		End Method




But then in my sprite system, I do this:

Local ThisSprite:Sprite

For ThisSprite = EachIn _List
ThisSprite.Free()
Next


Now, ThisSprite will be a sprite which is also a fruit, that was created when I said ThisFruit = New Fruit in my main program.

Will the Free method that is called there be the free method of the fruit type, or the free method of the sprite type?


I honestly don't know why I'm even trying to use this crap because it's practucally impossible to figure out what it will do.

I suspect I know what it will do. I suspect that it will call the sprite's Free method, and free the sprite. But that means then that my animation system will free the sprite when it is done animating, but it won't free the fruit itself. And that means that if you call Animate.Position() and pass it a fruit, and pass true for the parameter that tells it to free the sprite for you when it is done, then it will free the sprite, but the fruit won't be freed from it's own list, and that defeats the whole pupouse of the parameter, because now it's not fire and forget, cause if you just fire and forget that fruit will stay in that fruit list forever.

Ugh. What the hell is so great about OOP again? I never had this much difficulty learning how procedural programming worked, in any language, and the more I learn about this the more I doubt what people say about it preventing bugs. It makes it 10x more difficult to keep what is going on in your head, and it is not at all designed in such a way as to make bugs impossible. If I never had to keep anything in a list maybe, but obviously that's not realistic. <grumble>


N(Posted 2006) [#24]
What the hell is so great about OOP again?

I considered answering your question, but the way you're behaving makes me unwilling to do so. I honestly have to say it's like you're making no effort to learn this.


Dreamora(Posted 2006) [#25]
If a class implements an inherited feature, the new one is called (on the extended type)

exceptions are the constructor and destructor (new / delete), the are called on any of its super types to ensure that fields that were only declared for super are cleaned correctly as well.


sswift(Posted 2006) [#26]
Yeah, okay, I've been making no effort at all to learn it for the last two months I've been coding in Max day in and day out.

You know there's a reason I ended up coding in Blitz when I used to use C. It's called C++. I could never figure it out from looking at examples like I did all the other languages I used. So I stopped programming for 10 years. I only gfot back into programming games when I could do it without using C++ or any of those other OOP languiages, which meant in a way that didn't use DirectX.

The only reason I'm even trying to learn this oop stuff in Max is on the off chance that I might actually want or need to program in C++ someday. If I know how to do OOP in Max, it will be much easier to learn C++. Some of the stuff I've looked at in C++ actually makes a bit of sense now.

(I wouldn't worry about me switching to C++ anytime soon though. There's too many benefits to using Blitz.

Anyway I'm not unwiling to learn. I remain unconvinced it is WORTH learning though. I have learned quite a bit about it now, but I still don't see the benefits everyone is talking about. It's not reducing the number of bugs in my code. It's increasing them. My code is 95% bug free generally, but because of things like calling SetRotation and forgetting to put a . in front of it to tell it to use the global SetRotation and not the sprite's method of the same name, I get bugs that are very difficult to find.

(That could be prevented if I were forced to put Self. in front of every local method access, but I'm not. That may save a lot of typing, but it leads to bugs. I put # on all my floats even though I don't need to, because it prevents bugs if I know what the type is. If I see a variable without a # or a $, I assume it is an int.)


sswift(Posted 2006) [#27]
Dream:
So what you're saying is that ThisSprite.Free() will call ThisFruit.Free() instead if I am looking through my list of sprites, and it comes across a fruit, even though the type being used to iterate through the list is of type Sprite, like so?

For ThisSprite:Sprite = EachIn SpriteList
ThisSprite.Free()
Next

Will call the Fruit Free, rather than the Sprite Free, if that element of the list is a Fruit that is also a sprite?


(See this is something else that says to me that OOP sucks. Can you even understand what I've written up there? It's impossible to put into words!)


skn3(Posted 2006) [#28]
what is so good about oop ?
'class
Type Ccontrol Abstract
	Const inputbuffer_off = 0
	Const inputbuffer_press = 1
	Const inputbuffer_tick = 2
	Const inputbuffer_release = 3
	Const inputbuffer_held = 4

	Field inputbuffer_up
	Field inputbuffer_down
	Field inputbuffer_left
	Field inputbuffer_right
	Field inputbuffer_button1
	Field inputbuffer_button2
	Field inputbuffer_button3
	Field inputbuffer_button4
	Field inputbuffer_button5
	Field inputbuffer_button6
	
	Field inputtick_up
	Field inputtick_down
	Field inputtick_left
	Field inputtick_right
	Field inputtick_button1
	Field inputtick_button2
	Field inputtick_button3
	Field inputtick_button4
	Field inputtick_button5
	Field inputtick_button6
	
	Method SetButtons(button1,button2,button3,button4,button5,button6) Abstract
	Method SetAxis(axisup,axisdown,axisleft,axisright) Abstract
	Method Update() Abstract
End Type

'--------------------------------------------------------

'class
Type Ckeyboard Extends Ccontrol
	'keyboard input for a player
	Field key_up
	Field key_down
	Field key_left
	Field key_right
	Field key_button1
	Field key_button2
	Field key_button3
	Field key_button4
	Field key_button5
	Field key_button6
	
	Method SetButtons(button1,button2,button3,button4,button5,button6)
		'configures keyboard buttons
		key_button1 = button1
		key_button2 = button2
		key_button3 = button3
		key_button4 = button4
		key_button5 = button5
		key_button6 = button6
	End Method
	
	Method SetAxis(axisup,axisdown,axisleft,axisright)
		'configures keyboard axis
		key_up = axisup
		key_down = axisdown
		key_left = axisleft
		key_right = axisright
	End Method
	
	Method Update()
		'gets input from input source and stores in control input buffers
		inputbuffer_up = NewInputBufferState(key_up,inputbuffer_up,inputtick_up)
		inputbuffer_down = NewInputBufferState(key_down,inputbuffer_down,inputtick_down)
		inputbuffer_left = NewInputBufferState(key_left,inputbuffer_left,inputtick_left)
		inputbuffer_right = NewInputBufferState(key_right,inputbuffer_right,inputtick_right)
		inputbuffer_button1 = NewInputBufferState(key_jump,inputbuffer_button1,inputtick_button1)
		inputbuffer_button2 = NewInputBufferState(key_kick,inputbuffer_button2,inputtick_button2)
		inputbuffer_button3 = NewInputBufferState(key_punch,inputbuffer_button3,inputtick_button3)
		inputbuffer_button4 = NewInputBufferState(key_action,inputbuffer_button4,inputtick_button4)
		inputbuffer_button5 = NewInputBufferState(key_punch,inputbuffer_button5,inputtick_button5)
		inputbuffer_button6 = NewInputBufferState(key_action,inputbuffer_button6,inputtick_button6)
	End Method
		
	Method NewInputBufferState(key,buffer,tick)
		'reads curretn input buffer state and works out new state
		Select buffer
			Case Ccontrol.inputbuffer_off
				'check for key hit
				If KeyHit(key) 
					Return Ccontrol.inputbuffer_press
				Else
					Return Ccontrol.inputbuffer_off
				End If
			Case Ccontrol.inputbuffer_press
				'check for key held
				If KeyDown(key)
					Return Ccontrol.inputbuffer_held
				Else
					Return Ccontrol.inputbuffer_release
				End If
			Case Ccontrol.inputbuffer_tick
				'check for instant tick
				If KeyDown(key)
					If tick = 0
						Return Ccontrol.inputbuffer_tick
					Else
						Return Ccontrol.inputbuffer_held
					End If
				Else
					Return Ccontrol.inputbuffer_release
				End If
			Case Ccontrol.inputbuffer_release
				'reset key state
				Return Ccontrol.inputbuffer_off
			Default
				'check for key held
				If KeyDown(key)
					If (buffer-Ccontrol.inputbuffer_held) >= tick
						Return Ccontrol.inputbuffer_held + ((buffer-Ccontrol.inputbuffer_held) - tick)
					Else
						Return buffer+MilliSecs()
					End If
				Else
					Return Ccontrol.inputbuffer_release
				End If
		End Select
	End Method
End Type

'create class function
Function CreateKeyboardControl:Ccontrol()
	Local control:Ccontrol = New Ckeyboard
	Return control
End Function

local control:Ccontrol = CreateKeyboardControl()


Check this little nugget of code. I have a base controller object which must be extended. The keyboard implimentation will interpret keyboard input and dump universal (off,hit,tick,held,release) input states into the controller object. The program can then read these states from the controller object regardless of which input periphial it came from.

Further extensions of the control object could use joypad, mouse, network. It would just require making extended types for each of said input methods. Now my program can transparently refer to...

control.inputbuffer_up

... to find out the up state of a "controller".

Without OOP, a method like this would be doable but require a less intuative approach. The idea of using objects is just a different one to grasp, but in this example you can see that it gives you a "plug and play" ability which procedural languages can only fake.


sswift(Posted 2006) [#29]
I look at that and it looks like gobbeldygook.

Just because a design is greatly simplified, doesn't mean it is good. Assembly language is greatly simplified, but you would not write a game in it. OOP is greatly simplified in a different way, but is it a good way?

I could write a keyboard class that you could glance at and know intuitively how to use it almost immediately.

But I look at that, and you say you can use it for any type of input, but I can't see how to even use it for the keyboard. (I'm sure I could figure it out if I wrangled with it for a half hour, but that's the point. I shouldn't have to do that.)

It does you no good to make a class which can return the same input states for any number of different devices, if the code cannot be quickly and easily grasped fully.

If the point of OOP is to make it so other people can grasp your code so as to have a clear picture of what is going on in the program, then OOP appears to fail in this regard. If the point of OOP is only to make it harder to make buggy code, then I have not seen it do that, and at what cost? Hundreds of additional hours of people trying to figure out what the code does? Doesn't seem like a good tradeoff to me.

Also, I question the usefulness of this approach. A mouse is not a keyboard. A network is not a keyboard. A mouse is analog. A network has lag. You cannot simply pull an "up" state off a network and apply it to a player and have that work well. You might need to work under the assumption that that input was applied by the player 200 milliseconds ago, and calculate their current position based on that assumption.

Some things are better left unsimplified. Or at least, not oversimplified.

Anyway, I just don't see how you could say that doing something like this would require a less intuitive approach without OOP. You could so something like this with procedural programming and it would be way more intuitive.

There's a reason why Basic languages don't use OOP methods. OOP is less iniuitive than procedural programming. I taught myself procedural programming. Thus far I've failed to teach myself everything there is to know about OOP, and what I have learned about it so far is only thanks to people explaining it to me here. You might say I should have picked up a book on it to learn it, but if I have to do that itself is proof it is unintutuitive. Intuitive is derived from inituition, not from "read a book on it".


sswift(Posted 2006) [#30]
Well, forget all that stuff.

Maybe someone can explain how to do this in a good way, because I don't see it:

Type Sprite


	Global SpriteList:TList = CreateList()
	Field _Link:TLink


	Function Create:Sprite(Image:TImage=Null, Order%=0, Parent:Sprite=Null)
	
		Local NS:Sprite
			
		NS = New Sprite
						
		Return NS
	
	End Function	
	

	Method New()
					
		' Add sprite to the end of the sprite list, and save a pointer to the link created.
			Self._Link = SpriteList.AddLast(Self)
		
	End Method
	
	
	Method Free()
					
		' Remove sprite's link from sprite list. 
			RemoveLink _Link
			_Link = Null
											
	End Method

		
End Type



Type Animate

	Global AnimList:TList = CreateList()
	Field _Link:TLink
	
	Field _Sprite:Sprite
	
	Method Update(TimeElapsed%=0)
		
		' If this animation is in one-shot mode, and animation is at the end of its lifespan...
		
		If (_Flags & MODE_LOOP) = 0
			If (((_Flags & MODE_PINGPONG) = 0) And (_Age >= _Time)) Or (((_Flags & MODE_PINGPONG) = 1) And (_Age >= _Time*2))
						
				Select _FreeSprite 
					Case True  _Sprite.Free()	' Free sprite and stop all of its animations.
					Case False Free()			' Stop this animation only.
				EndSelect
						
			EndIf
		EndIf	 
	
	End Method	
	
End Type
		


Type Fruit Extends Sprite
	
	Global _List:TList = CreateList()
				
	Field ID%
	Field _Link:TLink	
			
	Method New()
		_Link = _List.AddLast(Self)
	End Method
	
	Method Free()
		RemoveLink _Link
		_Link = Null
	End Method		
		
End Type



The problem here is the Animate type. When it updates, if a flag is set, it should remove the sprite from the sprite list. This makes the sprite dissapear, and removes all references to it, so the sprite's memory is freed.

The problem though is if I create a Fruit, the fruit is placed in it's own list of fruit.

If what Dreamora is saying is correct, then when Animate.Update calls that Free() function, it will be calling the Fruit's Free() function, not the sprites.

If it does that, then that means the sprite that was added to the spritelist by the sprite's New method won't be removed from the sprite list, and so will continue to take up memory forever.

If on the other hand, the sprite's Free() function is the one that is called, then the FRUIT will never be freed because it is in it's own list.

So what is the appropriate way to deal with this, and what is actually going on with Free? Which Free is called by that animate.update() method?


REDi(Posted 2006) [#31]
It would be the fruits free method that gets called, but you get around it like this...
Type Test
	Method Free()
		Print "Deleted From Test"
	EndMethod
EndType

Type Test2 Extends Test
	Method Free()
		Super.Free()
		Print "Deleted From Test2"
	EndMethod
EndType

Local Tester:Test2 = New Test2
Tester.Free()



skn3(Posted 2006) [#32]
When I said in a more intuative way, i was refering to the larger picture of a game project. This is just one object, in an engine you would have many objects. Some would slot together, some would be isolated.

For example, you could have a player object extend a sprite object. The player object could contain a keyboard object extended from the controller object. The player could also contain an action replay recorder object used to track certain values (referenced with pointers) and bag object (for storing items). The bag object can have a list object that stores a collection of item objects. Each unique catergory of items (weapon, collectable, etc) would be extended from a base item object, which is also extended from our universal sprite object. Each of these objects would all contain their own routines, linking into the network of objects thta eventualy make up our runtime. The front end being the sprite object, which acts as the universal method of displaying and updating our objects. When you iterate through the list of sprites, all the chain reactions of objects updating, and calling their own routines will occur.

complicated ? .. lol yup, but thats what diagrams are for. Try drawing a map of the objects and things become clearer.

Thats what I meant. It really is just a different approach to programming. Its like anything really, until you have fully understood it, its not going to be easy to read it. Try reading a foreign language without knowing it.

As to your mentioned problem...

Type parent
	Method callme()
		Print "parent"
	End Method
End Type

Type child Extends parent
	Method callme()
		Print "child"
	End Method
End Type

Type child_without_callme Extends parent
End Type

Type child_with_callme_use_parent_callme Extends parent
	Method callme()
		Super.callme()
	End Method
End Type

Local a:parent 

a = New child
a.callme()

a = New parent
a.callme()

a = New child_without_callme
a.callme()

a = New child_with_callme_use_parent_callme
a.callme()

WaitKey()


this help ?


Robert Cummings(Posted 2006) [#33]
type physics
- put lots of physics functions in here to control physics
end type


type sphere extends physics
field obj
end type

nuff said


sswift(Posted 2006) [#34]
complicated ? .. lol yup, but thats what diagrams are for.


DIAGRAMS? Next you'll be telling me I should draw up flow charts!


Your example cleared things up a bit. Thanks for that.

But I have to call the sprite's free method from within the fruit's free method to make it work? That seems rather kludgy. That's what I was afraid of having to do.

Sure, maybe in this case it isn't that big a deal. But what if you have five levels of objects? I thought the point of OOP was that it's a no brainer to extend an object? This doesn't seem like a no brainer. This seems like the person extending the object would need to know how the object works in great detail before they extend it or else they'll have a bug that is hard to track down.

(Cue someone telling me that that's how you have to do it in BlitzMax, but there's a better way in C++.)


sswift(Posted 2006) [#35]
Well that tears it.

Type Fruit
   Function Create(FruitType%)
   End Function
End Type


"Overriding method differs by type."

I don't have time to screw around with this any more. If I can't have a create function for sprites that accepts different parameters than the create function for fruit, then that's just dumb. Anyway even if there's a way around the issue, these issues just keep popping up one after another and I have a game I have to finish. So forget this Extends crap. If I wasn't trying to make my sprite system and this thing object oriented I would have finished that sprite system a month earlier AT LEAST. It takes too damn long to design stuff around an oop paradigm. There comes a point when beauty meets reality, and I have to stop making the code prettier just for the sake of making it prettier, and get actual work done.


Liquid(Posted 2006) [#36]
I think the problem is that you don't fully understand all the OOP concepts yet. When you do, all will seem different/easier.


Dreamora(Posted 2006) [#37]
Sswift: That problem above has nothing to do with OO or anything the like, just with the missing possibility to reimplement functions or replacing them.

If you would have taken your time to learn BM first and start making a complex and very structure critical system then, you would have had much less problems. (most that are answering you are people that have been using BM since day 1 (I even installed PearPC and OSX on windows to test it) and that have been crawling through all BM sources to understand how it works and how to use the OO possibilities.
Thats what I would advice to you as well, especially as your system needs to be efficient.
Inefficient/Incorrect usage of the BRL modules can result in quite large performance loses.


sswift(Posted 2006) [#38]
That problem above has nothing to do with OO or anything the like, just with the missing possibility to reimplement functions or replacing them.


Aha! There it is!

(Cue someone telling me that that's how you have to do it in BlitzMax, but there's a better way in C++.)



If you would have taken your time to learn BM first and start making a complex and very structure critical system then, you would have had much less problems.


I did read up on the language. But there's only so much you, or I, at least, can absorb on a subject just by studying. I, and most people I think, learn best by doing.


Grey Alien(Posted 2006) [#39]
I, and most people I think, learn best by doing.

yeah me too. I had a major restructure of the game framework recently, but it IS much better now...


AlexO(Posted 2006) [#40]

I, and most people I think, learn best by doing.



but to get better you should be prepared to do it, sometimes from scratch, again...and again...and again...

The best thing you can do is implement things in small increments and always go back and see from what you've learned if you can re-implement things with your new found knowledge of OOP. I've done it several times already.

You might think its a waste of time once something 'works', but 3 months down the road when you need to change/add something you discover your initial design was poorly thought-out and takes even more effort to fix.

not sure if you do it or not, but you should look into 'Test Driven Development'. It lends itself a bit more towards OOP than procedural programming imo and really helps cut down on bugs.


If on the other hand, the sprite's Free() function is the one that is called, then the FRUIT will never be freed because it is in it's own list.

So what is the appropriate way to deal with this, and what is actually going on with Free? Which Free is called by that animate.update() method?



maybe this illustration will help you understand what works at RUNTIME.

SuperType
|
|
|
typeA extends SuperType
|
|
|
typeB extends typeA
|
|
|
typeC extends typeB


now imagine that SuperType has a free() method.

Then say you create a typeC somewhere in a mainloop or something. Now you have another function that expects a SuperType. So this function calls Free() on this object.

Now this is where it gets tricky:
example 1:
- imagine all the above types implement a Free() method

- the Free() method that gets called is the one that is CLOSEST to the actual type of the object. Looking at my type ladder above, since the ACTUAL type of the object is typeC it calls typeC.free() because it is the closest free() method to typeC.


example 2:
- all the types above EXCEPT typeC implement a free() method

- Again, take the CLOSEST free() method to typeC in that heiarchy ladder. This means typeB's free is the method that gets called on typeC.

I hope that helps you understand a bit better on how polymorphism and dynamic binding works.


sswift(Posted 2006) [#41]
Well, I find that with most things, like my sprite system, there is no way to design it out ahead of time. There are too many variables to consider. So I just start coding it, and then when I find out I need a certain feature, I add it. You might think this would reasult in a messy design, but it doesn't, because I'm really evolving the design as I go along, not just tacking things onto it. This sometimes requires me to go back and change code to make it work with the new setup, but not as often as you'd think because I consider lots of variables before I set down the initial design. But considering them all is impossible. At some point you just have to start coding.


Anyway since you've dug up this post, I might as well mention this...

It would have been great if we could have avoided having two different methods of creating new objects. Most of my objects have to have a create method because they need parameters passed. I don't know why Mark didn't just make New work like this:

Type Fruit

	Field Blah%

	Method New(NewBlah%)
		Blah = NewBlah
	End Method
	
End Type


ThisFruit:Fruit = Fruit.New(10)



So ThisFruit = New Fruit would no longer be allowed syntax, but instead to be consistent, you would use Fruit.New() by default, and be able to add parameters as a result. The New method in the type would, as now, automatically make a new instance of the type, which is accessable in the method with Self immediately.

This way we wouldn't have to make temporary vairables to hold the new type in a create function, and we wouldn't have a create function so there's two potential ways one might make a new type to consider. For example, you can do ThisList = CreateList() in Max. You can probalby also do ThisList = TList.Create(). But what happens if you do ThisList = New TList? Maybe it won't be initialized correctly. Who knows if the person that made the Create function did so by making a New Method first and having it called by the Create function so that both ways of creating the list work. You actually have to go into the TList method and look at it to see if you can use New or not. So it would be better if it were just standardized so one does not need to use create functions at all but can instead pass parameters to new.


AlexO(Posted 2006) [#42]

This way we wouldn't have to make temporary vairables to hold the new type in a create function, and we wouldn't have a create function so there's two potential ways one might make a new type to consider. For example, you can do ThisList = CreateList() in Max. You can probalby also do ThisList = TList.Create(). But what happens if you do ThisList = New TList



This definitely seems like an issue when dealing with other people's code. I guess I just always use a create() function if there is one out of habit. It seems that issue is more pronounced without decent documentation.


dmaz(Posted 2006) [#43]
This way we wouldn't have to make temporary vairables to hold the new type in a create function, and we wouldn't have a create function so there's two potential ways one might make a new type to consider. For example, you can do ThisList = CreateList() in Max. You can probalby also do ThisList = TList.Create(). But what happens if you do ThisList = New TList? Maybe it won't be initialized correctly. Who knows if the person that made the CreateList function did so by making a New Method first and having it called by the Create function so that both ways of creating the list work. You actually have to go into the TList method and look at it to see if you can use New or not. So it would be better if it were just standardized so one does not need to use create functions at all but can instead pass parameters to new.


that's what documentation is for! Tlist shouldn't of had a Create function because it adds nothing to the type. I initialize all my TList's with list = new TList, -I- find using createlist unintuitive. And a create method should only be implemented if the creation of the obj requires paramaters and then the documentation should clearly state proper procedure!

maybe OOP isn't for you. I've been programming for a long time in many languages... straight procedural languages suck in comparison. Don’t blame your lack of understanding on the language, something you seem to be in the habit of lately.

BTW: no you can't do list:TList = TList.create()


dmaz(Posted 2006) [#44]
another thing....
Anyway I'm not unwiling to learn. I remain unconvinced it is WORTH learning though. I have learned quite a bit about it now, but I still don't see the benefits everyone is talking about. It's not reducing the number of bugs in my code. It's increasing them. My code is 95% bug free generally, but because of things like calling SetRotation and forgetting to put a . in front of it to tell it to use the global SetRotation and not the sprite's method of the same name, I get bugs that are very difficult to find.

(That could be prevented if I were forced to put Self. in front of every local method access, but I'm not. That may save a lot of typing, but it leads to bugs. I put # on all my floats even though I don't need to, because it prevents bugs if I know what the type is. If I see a variable without a # or a $, I assume it is an int.)


forcing self would be redundant... this isn't really even an OOP issue, of course it going to use the sprite method how is that different than in Blitz3d? Local scope always comes first. anyway this is your fault for using the same name, but it's a perfectly fine way to do it, you just need to pay attention. you're getting more bugs because you're learning a different way to program. it's clear it hasn't all sunk in yet. You should really pick up a book on OOP.


sswift(Posted 2006) [#45]
"Don’t blame your lack of understanding on the language, something you seem to be in the habit of lately."

I always blame my lack of understanding on the language, that's nothing new. :-)

However, in this case, I'm not simply saying the stuff I don't understand sucks. I have been using methods inside types in my code, and while I kind of have gotten to LIKE that, I'm recognizing that I'm spending an awful lot of time on trying to make pretty interfaces to things when simple functions and arrays would have taken a few minute to implement. Creating a proper OOP like design takes a lot of extra throught and planning. The code might be neater with the use of some OOP related stuff in the long run, but I'm not sure all the delays are worth it. I am not convinced that the extra time it is taking to design these classes is merely the result of my inexperience with the concepts. I've got the basic stuff down fairly pat at this point.


"anyway this is your fault for using the same name,"

I would hardly call this "my fault". That makes it sound like I am doing something wrong by maintaining a consistent interface. Mark uses Set to set a field. I use Set to set a field. Mark uses SetRotation to set the angle at which to draw an image. I use SetRotation to set the angle at which to draw a sprite.

This is good design. It is consistent with the commands the user is familiar with. They do not need to remember that to set the angle of an image they have to use SetRotation, but to set the angle of a sprite they have to just use Angle().

Forcing Self isn't any more redundant than forcing me to write ThisSprite = Sprite(List.FirstLink.Value()) is redundant. Of COURSE I want to cast it to sprite, that's the type of the variable I'm passing it to! But it's there, presumably, for saftey reasons. And that's what requiring Self would be for.


"You should really pick up a book on OOP."

I understand the whole object concept just fine. A book is not going to convince me that this way of thinking is not backwards. The only way I will be convinced of that is if people provide compelling examples, or I discover it myself through the use of it in my code. However, though I understand how to use Extends, I am not using it right now.

I am using methods in types. I am even using function pointers. But using extends just seems to make life a royal pain in the ass.

I think the root of this problem is with how BlitzMax handles circularly linked references. I have a list of sprites in my sprite class. I need this list to be able to sort the sprites in the correct order for drawing. But because this list exists, in order to free a sprite I have to have the user call a function to remove it from the list. This is in direct conflict with the whole idea of an automatic garbage collector, and it means that if I extend the sprite's type with another type that ALSO keeps a list of it's members so they can be iterated through, then I now have to make sure the second type's free method frees not only itself, but also the sprites.

Then on top of this is that issue with the create function so now instead of just having a New function for the sprites, I have to also have a create function. But the fruit type also needs to have a create function, and the two functions have conflicting parameters. How do you solve that? Nobody told me. I think they indicated that my design was flawed if I had that issue, but that's crazy. It makes perfect sense for there to be a create function for sprites which takes an image parameter, and it makes perfect sense for fruit to have an entirely different create function which needs only an image parameter and a fruit type. Forcing me to work around this with some complicated scheme is just crazy and you can't tell me that that won't take a lot more time even if I know how to do it. It'll take a lot more time to make the code, and a lot more time to use it every time I need to.


dmaz(Posted 2006) [#46]
A book is not going to convince me that this way of thinking is not backwards.
well, something that is really backwards looking at something that is really forwards will see it backwards! ;)

But the fruit type also needs to have a create function, and the two functions have conflicting parameters.

This is a flaw in the current language implementation and should be fixed because it is very limiting... so no, you're not crazy with that one. This is a problem that actually forces a developer to 'hack' the base class. IMO this is the worst problem with the current design.


FlameDuck(Posted 2006) [#47]
Creating a proper OOP like design takes a lot of extra throught and planning.
That depends on your approach. Ofcourse it isn't improved by your tendancies to over-engineer complex solutions for simple problems. Your goal should not be to create elaborate object hierarchies. "Your goal should be to create the simplest solution, that could possibly work" - Kent Beck.

I understand the whole object concept just fine.
I beg to differ. Let me suggest three good and enlightening (and often conflicting) books on the subject (and the order you should read them in):

Craig Larman, Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design.

Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides), Design Patterns: Elements of Reusable Object-Oriented Software.

Kent Beck, Extreme Programming Explained: Embrace Change.

I am even using function pointers.
See? Function pointers aren't typesafe - thus not OO. If you where using a delegate, it would be OO. Sadly however BlitzMAX does not currently support type-safe callbacks.

IMO this is the worst problem with the current design.
The lack of proper access modifiers is by far the worst flaw in the current design. Also the way that not everything is an object contributes somewhat to the lack of consistent languge structure.


Brucey(Posted 2006) [#48]
Also the way that not everything is an object contributes somewhat to the lack of consistent languge structure.

While you're there, you should bring that little tidbit up with sun/java too :-p

Creating a proper OOP like design takes a lot of extra throught and planning.

I think the word "design" is kind of key here...
In that, if you sit down and design something first, rather than design it as you type in your code, it might be easier to foresee some issues before you've written chunks that you then realise need to be changed.

But then again, who - in the real world - actual does a full design these days?

And what's UML all about?

:o)


FlameDuck(Posted 2006) [#49]
While you're there, you should bring that little tidbit up with sun/java too :-p
Yeah, and C#, but BlitzMAX is far worse. At least in Javas defense it was designed by comitee, with lots of different people pulling in different directions. BRL shouldn't have this problem.

And what's UML all about?
Unified Modeling Language was designed by Rational Software (now IBM) to supplement the Rational Unified Process, which is a heavyweight development process (unlike XP which is light-weight or agile). It consists of a collection of diagrams that helps developers visualize their program before writting them. Going from an UML design to OO implementation, is a trivial matter. Ofcouirse then the issue is to which degree one wants to use upfront design in ones project.

As for who does them these days? Mostly large teams. Everyone else uses a reengineering tool (like Borland Together or Microsoft Visio) to generate UML diagrams from sourcecode. This helps them locate any "that doesn't look like how we wanted it" issues.

Most people still use a "code-and-fix" approach to programing, and it usually shows on the comparative quality of their software.