Page 2 of 6

Posted: Thu Feb 16, 2006 10:29 am
by El_Choni
Something like this, perhaps?:

Code: Select all

Prototype PROCTYPE(a, b, c)

Structure OBJECT
  a.l
  b.w
  proc.PROCTYPE
EndStructure

Procedure MyObjectProcedure(*this.OBJECT, a, b)
  Debug "*this\a: "+Str(*this\a)
  Debug "TEST a+b ="+Str(a+b)
EndProcedure

MyObject.OBJECT
MyObject\proc = @MyObjectProcedure()
MyObject\a = 16

MyObject\proc(MyObject, 1, 2) 

Posted: Thu Feb 16, 2006 10:35 am
by Psychophanta
In fact, i see it is possible to forget Interfaces and do the same stuff (but even faster) using Prototypes instead!

Posted: Thu Feb 16, 2006 11:02 am
by Psychophanta
Here is a "Prototyped" version of the first fsw idea.
This way no needs for Datasection neither Interfaces, but Prototypes.
Even i don't know if it is so elegant OOP code, but it is probably faster :)

Code: Select all

;example with Properties - now we have Methods and Properties
;Original (Interface version) with "Labels/address in datasection" by fsw
;Prototyped modified version by Psychophanta.

;define Methods
Prototype PROCTYPE1(a,b)
Prototype PROCTYPE2(a)

;define Properties
Structure MyObject
   VTable.l ;this is needed to store the address of the Methods vtable
   ValueOfThat.l ;start with the properties
   ;add more properties if needed
   ;Methods:
   StoreThis.PROCTYPE1;  <-  StoreThis(par.l)
   GetThat.PROCTYPE2;  <-  GetThat()
   ;add more if needed
EndStructure

;this method/procedure can have a different name than in the interface
;the only thing that is important is the position in the vtable
Procedure This(*Self.MyObject, par.l)
   *Self\ValueOfThat = par
   MessageRequester("MyObject", "This: " + Str(par))
EndProcedure

;this method/procedure can have a different name than in the interface
;the only thing that is important is the position in the vtable
Procedure That(*Self.MyObject)
   MessageRequester("MyObject", "That: " + Str(*Self\ValueOfThat))
EndProcedure

;Create a class list
;this way there can be more than one object of a class
Global NewList MyObject_List.MyObject()

Procedure.l CreateThisClass()
   AddElement(MyObject_List())
   MyObject_List()\StoreThis = @This()
   MyObject_List()\GetThat = @That()
   ProcedureReturn @MyObject_List()
EndProcedure

;- start main:

CreateThisClass()

MyObject_List()\StoreThis(@MyObject_List(),347)
MyObject_List()\GetThat(@MyObject_List())

Posted: Thu Feb 16, 2006 7:53 pm
by fsw
Now I have to give my humble opinion.

Eddy's first example is not oop at all because he never uses the variables inside the structure (Eddy recognizes itself later on that's it's not oop...).

----------------------------------------------

El_Choni's example is good, but is not scalable. With scalable I mean all Procedures need to be set AFTER the OBJECT is initialized.

In real life situation this would result in:

Code: Select all

;- start main
;user creates his object named MyObject
MyObject.OBJECT

;now the user HAS TO SET all procs to get it working
MyObject\proc1 = @MyObjectProcedure1()
MyObject\proc2 = @MyObjectProcedure2()
MyObject\proc3 = @MyObjectProcedure3()
MyObject\proc4 = @MyObjectProcedure4()
MyObject\proc5 = @MyObjectProcedure5()
MyObject\proc6 = @MyObjectProcedure6()
;and even more procs

;the user can set the properties of object
MyObject\a = 16

;the user can call his methods of object
MyObject\proc(MyObject, 1, 2) 
If the setting of all the Procedures/Methods could be done before the user assigns a class to his object (MyObject.OBJECT) this solution would be another good way to do it.

----------------------------------------------

Phantas example is a also good but MAIN actually starts with GLOBAL NEWLIST.
So users code would look like:

Code: Select all

;- start main
;user has to create a list
Global NewList MyObject_List.MyObject()

;user has to set the procedures/ methods
Procedure.l CreateThisClass()
   AddElement(MyObject_List())
   MyObject_List()\StoreThis = @This()
   MyObject_List()\GetThat = @That()
   ProcedureReturn @MyObject_List()
EndProcedure

;user initializes his class
;List name is "hard wired" and can't be changed on the fly
CreateThisClass()

;user is calling his methods
;code seems to be pretty long
MyObject_List()\StoreThis(@MyObject_List(),347)
MyObject_List()\GetThat(@MyObject_List()) 
If Phantas code can be changed to:

Code: Select all

;- start main
;user has to create a list
Global NewList MyObject_List.MyObject()

;user initializes his class
;here all procedures/methods are initialized automatically
CreateThisClass(@MyObject_List())

;user is calling his methods
MyObject_List()\StoreThis(347)
MyObject_List()\GetThat()
this solution would be another good way to do it.

----------------------------------------------

But now look at the main code of my first post:

Code: Select all

;- start main:
;user creates his object named "m" as "MyObject" class
;user initializes his class with "CreateThisClass()"
;all Procedures/Methods are set automatically
m.MyObject = CreateThisClass()

;user uses his methods
m\StoreThis(347)
m\GetThat()

End 
For the user the last code is far more simpler to use.
The Properties can only be accessed through Methods, which is actually not a bad thing.

Maybe the same can be achieved with prototypes...

Hope I didn't upset anybody, it wasn't my intention.

In fact if there is a simpler oop way using prototypes instead of interfaces this would be great.

Please keep the ideas coming... :D

Posted: Thu Feb 16, 2006 9:37 pm
by Kale
El_Choni wrote:Something like this, perhaps?:

Code: Select all

Prototype PROCTYPE(a, b, c)

Structure OBJECT
  a.l
  b.w
  proc.PROCTYPE
EndStructure

Procedure MyObjectProcedure(*this.OBJECT, a, b)
  Debug "*this\a: "+Str(*this\a)
  Debug "TEST a+b ="+Str(a+b)
EndProcedure

MyObject.OBJECT
MyObject\proc = @MyObjectProcedure()
MyObject\a = 16

MyObject\proc(MyObject, 1, 2) 
Thats Great!!! :D

Posted: Thu Feb 16, 2006 10:18 pm
by Kale
Ok, El Choni, WTF have i done wrong here? :x

EDIT: finished code below.

Posted: Thu Feb 16, 2006 10:35 pm
by netmaestro
You didn't pass the object to the method as parameter. When using the prototype approach, this is necessary.

Posted: Thu Feb 16, 2006 10:53 pm
by fsw
netmaestro wrote:Kale, your prototype for InitialiseSkull is missing a parameter for the object to be passed.

Code: Select all

Prototype INITIALISESKULL(object, SpriteNumber.l, ScreenWidth.l, ScreenHeight.l) 

and pass the variable of type SKULL_CLASS:

Skulls\InitialiseSkull(skulls, 1, ScreenWidth, ScreenHeight) 

and it should work. (it does here)
This is correct.

@Kale, you set the procs before it's even defined.

this can't work:

Code: Select all

Skulls\EchoValues = @EchoValues()
Skulls\MoveSkull = @MoveSkull()
Skulls\InitialiseSkull = @InitialiseSkull()
Skulls\DisplaySkull = @DisplaySkull()

;Object Instance
Global Skulls.SKULL_CLASS

Skulls\InitialiseSkull(1, ScreenWidth, ScreenHeight)
Skulls\EchoValues()

End 
this does:

Code: Select all

;Object Instance
Global Skulls.SKULL_CLASS

Skulls\EchoValues = @EchoValues()
Skulls\MoveSkull = @MoveSkull()
Skulls\InitialiseSkull = @InitialiseSkull()
Skulls\DisplaySkull = @DisplaySkull()

Skulls\InitialiseSkull(Skulls, 1, ScreenWidth, ScreenHeight)
Skulls\EchoValues()

End 
As you can see with prototypes (as it is now) you have to use your object as 1st parameter:

Code: Select all

Skulls\InitialiseSkull(Skulls, 1, ScreenWidth, ScreenHeight) 
which is actually oop-like but not oop.

With interfaces it would look like:

Code: Select all

Skulls\InitialiseSkull(1, ScreenWidth, ScreenHeight) 
which is oop.

Posted: Thu Feb 16, 2006 11:08 pm
by Bonne_den_kule
Maybe someone could make a new OOP tutorial for PB 4.00 :P

Posted: Thu Feb 16, 2006 11:32 pm
by Truth_Seeker
Is there anyway for this to be used to allow us to interface or access libraries made in C++ (that use oop)? Or is this all just fake oop?

Posted: Thu Feb 16, 2006 11:40 pm
by Kale
Thanks NetMaestro and FSW! I must of had too much red wine tonight! :P Anyway heres what i was working on:

Code: Select all

EnableExplicit

;===========================================================================
;-PROCEDURES
;===========================================================================

;Handle an error
Procedure HandleError(Result, Text.s)
	If Result = 0 : MessageRequester("Error", Text, #PB_MessageRequester_Ok) : End : EndIf
EndProcedure

;===========================================================================
;-CONSTANTS
;===========================================================================

#APP_NAME = "Skulls"
#APP_VERSION = "v0.1a"

#NUMBER_OF_SPRITES = 100
#SPRITE_IMAGE = 1
#SPRITE_SIZE = 256

Enumeration
	#WINDOW_ROOT
EndEnumeration

;===========================================================================
;-VARIABLES / FLAGS / VARIABLES / STRUCTURES / ARRAYS
;===========================================================================

HandleError(ExamineDesktops(), "Failed to examine any desktops")
Define ScreenWidth.l : ScreenWidth = DesktopWidth(0)
Define ScreenHeight.l : ScreenHeight = DesktopHeight(0)

Global x.l

;===========================================================================
;-SKULL CLASS
;===========================================================================

Prototype ECHOVALUES(Object.l)
Prototype MOVESKULL(Object.l, ScreenWidth.l, ScreenHeight.l)
Prototype INITIALISESKULL(Object.l, SpriteNumber.l, ScreenWidth.l, ScreenHeight.l)
Prototype DISPLAYSKULL(Object.l)

;Class Definition
Structure SKULL_CLASS
	EchoValues.ECHOVALUES
	MoveSkull.MOVESKULL
	InitialiseSkull.INITIALISESKULL
	DisplaySkull.DISPLAYSKULL
	SpriteNumber.l
	XPos.f
	YPos.f
	XStep.f
	YStep.f
	Alpha.l
EndStructure

;Class Method Definitions
Procedure EchoValues(*this.SKULL_CLASS)
	Debug "Sprite Number: " + Str(*this\SpriteNumber)
	Debug "XPos: " + StrF(*this\XPos)
	Debug "YPos: " + StrF(*this\YPos)
	Debug "XStep: " + StrF(*this\XStep)
	Debug "YStep: " + StrF(*this\YStep)
	Debug "Alpha: " + Str(*this\Alpha)
	Debug ""
EndProcedure

Procedure MoveSkull(*this.SKULL_CLASS, ScreenWidth.l, ScreenHeight.l)
	*this\YPos + *this\YStep
	*this\XPos + *this\XStep
	If *this\YPos > ScreenHeight + #SPRITE_SIZE / 2
		*this\YPos = 0 - #SPRITE_SIZE
		*this\XPos = Random(ScreenWidth) - #SPRITE_SIZE / 2
		*this\Alpha = Random(128)
	EndIf
EndProcedure

Procedure InitialiseSkull(*this.SKULL_CLASS, SpriteNumber.l, ScreenWidth.l, ScreenHeight.l)
	*this\SpriteNumber = SpriteNumber
	*this\XPos = Random(ScreenWidth)
	*this\YPos = Random(ScreenHeight)
	*this\YStep = 1 + Random(5)
	If *this\YStep >= 3
		*this\XStep = 1 + Random(5)
	Else
		*this\XStep = 1 - Random(5)
	EndIf
	*this\Alpha= Random(128)
EndProcedure

Procedure DisplaySkull(*this.SKULL_CLASS)
	DisplaySprite3D(*this\SpriteNumber, *this\XPos, *this\YPos, *this\Alpha)
EndProcedure

;Object Instances
Global Dim Skulls.SKULL_CLASS(#NUMBER_OF_SPRITES)

;Initialise All Objects
For x = 0 To #NUMBER_OF_SPRITES
	Skulls(x)\EchoValues = @EchoValues()
	Skulls(x)\MoveSkull = @MoveSkull()
	Skulls(x)\InitialiseSkull = @InitialiseSkull()
	Skulls(x)\DisplaySkull = @DisplaySkull()
	Skulls(x)\InitialiseSkull(Skulls(x), x, ScreenWidth, ScreenHeight)
	;Skulls(x)\EchoValues(Skulls(x))
Next x

;===========================================================================
;-GEOMETRY
;===========================================================================

HandleError(InitSprite(), "Error Initialising DirectX:"+Chr(10)+"Microsoft DirectX v7.0 or greater must be installed correctly."+Chr(10)+"Offending Command: InitSprite()")
HandleError(InitKeyboard(), "Error Initialising DirectX:"+Chr(10)+"Microsoft DirectX v7.0 or greater must be installed correctly."+Chr(10)+"Offending Command: InitKeyboard()")
HandleError(InitMouse(), "Error Initialising DirectX:"+Chr(10)+"Microsoft DirectX v7.0 or greater must be installed correctly."+Chr(10)+"Offending Command: InitMouse()")
HandleError(InitSprite3D(), "Error Initialising DirectX:"+Chr(10)+"Microsoft DirectX v7.0 or greater must be installed correctly."+Chr(10)+"Offending Command: InitSprite3D()")
Sprite3DQuality(0)

SetRefreshRate(60)
HandleError(OpenScreen(ScreenWidth, ScreenHeight, 32, #APP_NAME + " " + #APP_VERSION), "A " + Str(ScreenWidth) + "x" + Str(ScreenHeight) + " 32bit fullscreen screen could not be opened!"+Chr(10)+"Microsoft DirectX v7.0 Or greater must be installed correctly.")
SetFrameRate(60)

CatchSprite(#SPRITE_IMAGE, ?SpriteImage, #PB_Sprite_Texture)
TransparentSpriteColor(#SPRITE_IMAGE, 0)

For x = 0 To #NUMBER_OF_SPRITES
    CreateSprite3D(x, #SPRITE_IMAGE)
    ZoomSprite3D(x, #SPRITE_SIZE, #SPRITE_SIZE)
Next x

;===========================================================================
;-MAIN LOOP
;===========================================================================

HandleError(Start3D(), "Error Initialising DirectX:"+Chr(10)+"Microsoft DirectX v7.0 or greater must be installed correctly."+Chr(10)+"Offending Command: Start3D()")

Repeat

    ClearScreen(123)
    Sprite3DBlendingMode(5, 7)
    
    For x = 0 To #NUMBER_OF_SPRITES
        Skulls(x)\DisplaySkull(Skulls(x))
        Skulls(x)\MoveSkull(Skulls(x), ScreenWidth, ScreenHeight)
    Next x

    ExamineKeyboard()
    ExamineMouse()

    Delay(1)
    
    FlipBuffers()
    
Until KeyboardPushed(#PB_Key_All) Or MouseDeltaX() <> 0 Or MouseDeltaY() <> 0 Or MouseWheel() <> 0 Or MouseButton(1) <> 0 Or MouseButton(2) <> 0 Or MouseButton(3) <> 0
Stop3D()

End

;===========================================================================
;-BINARY INCLUDES
;===========================================================================

DataSection
    SpriteImage:
        IncludeBinary "Skull.bmp"
EndDataSection

Image:
http://www.garyw.uklinux.net/images/Skull.bmp

I love this approach of simple OOP with using structures and prototypes! I wonder if simple inheritance is possible using the 'Extends' keyword? ;)

My next game project will all be wrote like this! :twisted:

Posted: Thu Feb 16, 2006 11:44 pm
by Fred
Using interfaces allows to skip the repetition of the first argument, which is a must, as it looks much better IMHO (and is faster).

Posted: Thu Feb 16, 2006 11:46 pm
by Kale
Fred wrote:Using interfaces allows to skip the repetition of the first argument, which is a must, as it looks much better IMHO (and is faster).
Aye but surely implementing Virtual Tables can be done more elegantly. I hate the way you have to create vtables using interfaces. And using interfaces, the properties are seperate from methods. leading to an interface declaration and properties structure plus i can't find a way to set property values without an interface method. :?

Posted: Thu Feb 16, 2006 11:53 pm
by netmaestro
Yes, the repetition of the argument is not cool.

I think the future of OOP in PureBasic is going to look very much like fsw's first example. The vtable approach is really quite clean and efficient once you try it out.

Posted: Fri Feb 17, 2006 12:36 am
by Fred
Kale wrote:plus i can't find a way to set property values without an interface method. :?
In some object langage like java, it's almost forbidden to access a property without encapsulate it. It makes the things more modular and easier to maintain (for example if you need to add a check later, it's easier to put it in the function, things you can't do when you allow to affect directly a value to a structure element).