Page 1 of 1

Simple OOP (with module, without interface or vtable)

Posted: Sun May 31, 2015 9:46 pm
by eddy
  • Example1 Features:
    • public get/set methods
    • constructor
    • instanciate/free object instance
    Example2 Features:
    • private method used by public method
    • overriden default FreeInstance method
    • static method to count objects
    Example3 Features:
    • class inheritance

Code: Select all

DeclareModule OOP
   Macro _CONCAT(a=, b=) ; helpers used to escape some MACRO variable
      a#b
   EndMacro
   
   Macro _DQUOTE ; helpers used to build MACRO string
      "
   EndMacro
   
   Macro _COLON ; helpers used to construct nested MACRO
      : 
   EndMacro
   
   Macro UseInstanceType(T) ; determine which instance TYPE will be implemented
      Procedure.i DefaultFreeInstance_#T()
         UseInstance(*this.T)
         ClearStructure(*this.T, T)
         ProcedureReturn #Null
      EndProcedure
      UndefineMacro _CONCAT(INSTANCE, _TYPE)
      _COLON#Macro _CONCAT(INSTANCE, _TYPE)_COLON#T#_COLON#EndMacro
   EndMacro
   
   Macro Instanciate(This, ParentType=OOP::OBJECT, InstanciateParent=#Null) ; create NEW instance
      Protected *parent.ParentType=InstanciateParent
      Protected This=AllocateStructure(INSTANCE_TYPE)
      If *parent
         CopyStructure(*parent, This, ParentType)
         This\InstanceParentType$=*parent\InstanceType$
      Else
         This\InstanceParentType$="OBJECT"
      EndIf
      This\InstanceType$=_DQUOTE#INSTANCE_TYPE#_DQUOTE
      This\FreeInstance=@DefaultFreeInstance_#INSTANCE_TYPE()
      This\DefaultFreeInstance=@DefaultFreeInstance_#INSTANCE_TYPE()
   EndMacro
   
   Macro SetInstanceMethod(Method) ; init NEW instance method
      *this\Method=@Method()
   EndMacro
   
   Macro UseInstance(This) ; provide THIS instance to module method
      CompilerIf Not Defined(_CONCAT(*th, is), #PB_Variable)
         Protected This
         CompilerIf #PB_Compiler_Processor=#PB_Processor_x64
            ! MOV [p.p_this], Rbp
         CompilerElse
            ! MOV [p.p_this], Ebp
         CompilerEndIf
      CompilerEndIf
   EndMacro
   
   Prototype.i FreeInstance()
   Structure OBJECT ; instance base TYPE
      InstanceParentType$
      InstanceType$
      FreeInstance.FreeInstance
      DefaultFreeInstance.FreeInstance
   EndStructure
EndDeclareModule
Module OOP : EndModule
Macro FreeInstance(This)
   This=This\OOP::_CONCAT(Free, Instance)()
EndMacro

CompilerIf #PB_Compiler_IsMainFile
   ; ***********************
   ; EXAMPLE 1
   ; ***********************
   DeclareModule USER
      UseModule OOP
      Prototype SetName(name.s)
      Prototype.s GetName()
      
      Structure USER Extends OBJECT
         ;- public methods
         SetName.SetName
         GetName.GetName
         ;- public properties
         Name$
      EndStructure
      ;- constructor
      Declare.i InstanciateUSER()
   EndDeclareModule
   
   Module USER
      UseModule OOP
      UseInstanceType(USER)
      
      Procedure.s GetName()
         UseInstance(*this.USER)
         ProcedureReturn *this\Name$
      EndProcedure
      
      Procedure SetName(name.s)
         UseInstance(*this.USER)
         *this\Name$=name
      EndProcedure
      
      Procedure.i InstanciateUSER()
         Instanciate(*this.USER)
         SetInstanceMethod(GetName)
         SetInstanceMethod(SetName)
         *this\Name$="No name"
         ProcedureReturn *this
      EndProcedure
   EndModule
   
   ;////// create and manipulate user object
   UseModule USER
   Define *user.USER=InstanciateUSER()
   *user\SetName("Toto")
   Debug *user\GetName()
   FreeInstance(*user)
CompilerEndIf

Re: OOP with module without interface

Posted: Sun May 31, 2015 9:47 pm
by eddy

Code: Select all

CompilerIf #PB_Compiler_IsMainFile
   ; ***********************
   ; EXAMPLE 2
   ; ***********************
   
   DeclareModule CAR
      UseModule OOP
      Prototype.s GetModelRef()
      Prototype.f Move(dx, dy)
      
      Structure CAR Extends OBJECT
         ;- public methods
         GetModelRef.GetModelRef
         Move.Move
         ;- public properties
         ModelRef.s
         x.i
         y.i
      EndStructure
      ;- constructor
      Declare.i InstanciateCAR(Owner$)
      ;- static method
      Declare.i CountCars()
   EndDeclareModule
   
   Module CAR
      UseModule OOP
      UseInstanceType(CAR)
      Global CountCars=0
      
      Procedure.i FreeInstance() ;- private method
         UseInstance(*this.CAR)
         Debug "This car will be destroyed: " + *this\ModelRef + ""
         CountCars - 1
         ProcedureReturn *this\DefaultFreeInstance()
      EndProcedure
      
      Procedure.f CalcDistance() ;- private method
         UseInstance(*this.CAR)
         ProcedureReturn Sqr(Pow(*this\x, 2) + Pow(*this\y, 2))
      EndProcedure
      
      Procedure.s GetModelRef()
         UseInstance(*this.CAR)
         ProcedureReturn *this\ModelRef
      EndProcedure
      
      Procedure.f Move(dx, dy)
         UseInstance(*this.CAR)
         *this\x + dx
         *this\y + dy
         ProcedureReturn CalcDistance()
      EndProcedure
      
      Procedure.i InstanciateCAR(ModelRef$)
         Instanciate(*this.CAR)
         SetInstanceMethod(FreeInstance) ;- overriden method (default method was DefaultFreeInstance)
         SetInstanceMethod(GetModelRef)
         SetInstanceMethod(Move)
         With *this
            \ModelRef=ModelRef$
            \x=0
            \y=0
         EndWith
         CountCars + 1
         ProcedureReturn *this
      EndProcedure
      
      Procedure.i CountCars()
         ProcedureReturn CountCars
      EndProcedure
   EndModule
   
   ;////// create and manipulate CAR objects
   UseModule CAR
   Define *first.CAR=InstanciateCAR("Ford")
   Define *second.CAR=InstanciateCAR("Tesla")
   Debug "Instance Pointer=" + #TAB$ + *second
   Debug "Instance Type$=" + #TAB$ + *second\InstanceType$
   Debug "Current ModelRef=" + #TAB$ + *second\GetModelRef()
   Debug "Current Distance=" + #TAB$ + *second\Move(10, 5)
   Debug "Current Location=" + #TAB$ + *second\x + "," + *second\y
   Debug "Count Cars=" + CAR::CountCars()
   FreeInstance(*second)
   Debug "Count Cars=" + CAR::CountCars()
   If *second=#Null : Debug "Second car has been destroyed!" : EndIf
CompilerEndIf

Re: Simple OOP (with module, without interface or vtable)

Posted: Mon Jun 01, 2015 12:33 am
by eddy

Code: Select all

CompilerIf #PB_Compiler_IsMainFile
   ; ***********************
   ; EXAMPLE 3
   ; ***********************
   DeclareModule ENEMY
      UseModule OOP
      Prototype.s Shoot(projectile.s)
      Prototype.s ShootLaser(Color.s)
      
      ;- PARENT object
      Structure MACHINE Extends OBJECT 
         Shoot.Shoot
         Health.i
         ShotProjectile.s
      EndStructure
      Declare.i InstanciateMACHINE(Health=10)
      
      ;- CHILD object based on PARENT object
      Structure ROBOT Extends MACHINE 
         ShootLaser.ShootLaser
         Speed.i
      EndStructure
      Declare.i InstanciateROBOT(Health, Speed)
   EndDeclareModule
   
   Module ENEMY
      UseModule OOP      
      UseInstanceType(MACHINE)      
      
      Procedure.s Shoot(projectile.s)
         UseInstance(*this.MACHINE)
         *this\ShotProjectile=projectile + " has been shot!"
         ProcedureReturn "Shooting " + projectile + "..."
      EndProcedure      
      
      Procedure.i InstanciateMACHINE(Health=10)
         Instanciate(*this.MACHINE) ;- init instance of PARENT object
         SetInstanceMethod(Shoot)
         *this\Health=Health
         ProcedureReturn *this
      EndProcedure
   
      UseInstanceType(ROBOT)      
      
      Procedure.s ShootLaser(Color.s="Red")
         UseInstance(*this.ROBOT)
         ProcedureReturn *this\Shoot(Color + " Laser")
      EndProcedure
      
      Procedure.i InstanciateROBOT(Health, Speed)
         Instanciate(*this.ROBOT, MACHINE, InstanciateMACHINE(Health)) ;- init instance of CHILD object via Inheritance
         SetInstanceMethod(ShootLaser)
         *this\Speed=Speed
         ProcedureReturn *this
      EndProcedure
   EndModule
   
   ;////// create and manipulate MACHINE and ROBOT objects
   Define *machine.ENEMY::MACHINE=ENEMY::InstanciateMACHINE()
   Debug #LF$ + "This " + *machine\InstanceType$ + " is a kind of " + *machine\InstanceParentType$
   Debug *machine\Health
   Debug *machine\Shoot("MISSILE")
   Debug *machine\ShotProjectile
   FreeInstance(*machine)
   Define *robot.ENEMY::ROBOT=ENEMY::InstanciateROBOT(100, 150)
   Debug #LF$ + "This " + *robot\InstanceType$ + " is a kind of " + *robot\InstanceParentType$
   Debug *robot\Health
   Debug *robot\Speed
   Debug *robot\ShootLaser("GREEN")
   Debug *robot\ShotProjectile
   FreeInstance(*robot)
CompilerEndIf

Re: Simple OOP (with module, without interface or vtable)

Posted: Mon Jun 01, 2015 9:02 pm
by Kwai chang caine
Perhaps a new reason for like OOP a day :wink:
Thanks for sharing 8)

Re: Simple OOP (with module, without interface or vtable)

Posted: Thu Jun 04, 2015 7:32 am
by idle
looks interesting, thanks for sharing.

Re: Simple OOP (with module, without interface or vtable)

Posted: Thu Jun 04, 2015 11:33 am
by eddy
I'm looking for elegant way to manage type casting

Re: Simple OOP (with module, without interface or vtable)

Posted: Mon Nov 30, 2015 8:34 pm
by Lunasole
Wow, that seems to be perfect & tiny user-solution for some basic OOP like "class modules". Don't know why I didn't seen this thread before (just remember how tried outdated precompilers for PB 4.x).
Hope it will work fine on practice. Thanks for posting anyway

Re: Simple OOP (with module, without interface or vtable)

Posted: Tue Dec 01, 2015 6:17 pm
by GPI
What exactly does this lines:

Code: Select all

       Macro UseInstance(This) ; provide THIS instance to module method
          CompilerIf Not Defined(_CONCAT(*th, is), #PB_Variable)
             Protected This
             CompilerIf #PB_Compiler_Processor=#PB_Processor_x64
                ! MOV [p.p_this], Rbp
             CompilerElse
                ! MOV [p.p_this], Ebp
             CompilerEndIf
          CompilerEndIf
       EndMacro

Re: Simple OOP (with module, without interface or vtable)

Posted: Tue Dec 08, 2015 7:33 pm
by NicTheQuick
I am also quite interested in what that Macro does.

Re: Simple OOP (with module, without interface or vtable)

Posted: Tue Dec 08, 2015 11:32 pm
by Lunasole
Well I had to spend some time trying to understand how all this magic works before using it.
There are some limits if I'm not wrong:
- cannot use static variables inside of class methods : they will be the same for all instances
- cannot define private properties inside class : they will be the same for all instances

So, all properties should be defined in "public" and always are visible. That is a bit unhandy, but in total not a minus, cause it follows from authors system is not complicated and there is no full inheritance of all class code, so no speed lost / memory overhead.

What about *this pointer, that's so damn nice hack 8) . I just got it gets instance-related structure pointer from EBP register [if 32-bit app] on every method call and defines it as protected variable inside method, but how it all works exactly on lower level I didn't get. Maybe someone sitting with assembler can explain.

Re: Simple OOP (with module, without interface or vtable)

Posted: Wed Dec 09, 2015 8:27 am
by Lunasole
After other tests, just exposed some another limit: passing structure fields as arguments to public methods leads to memory corruption and Purifier's stop message "Overflow in the global data block."

For example, try such code with "Example 2":

Code: Select all

Structure Problem 
	A0.l
	A1.l
	A2.l
	A3.l
	A4.l
	A5.l
	A6.l
	A7.l
EndStructure
Global F.Problem

*second\Move(F\A0, 20)
Very sad, that limit seems to be serious crap unlike previous. If sending regular variables (not structure members) there is no such problem.

Re: Simple OOP (with module, without interface or vtable)

Posted: Thu Dec 10, 2015 2:06 pm
by Lunasole
I spend this day messing with assembler and trying to find a way to get pointer of structure which calls method, but this all seems to be a bullshit.
Sometimes it's pointer comes in EBP/RBP register, in such cases this Simple OOP works fine.
In some other cases I was able to get address this way, it also was fine

Code: Select all

Protected Mark.l ; declared right at procedure beginning
CopyMemory(@Mark - 8, @This, 4)
But in all others PB gives such a shit where that address not even mentioned in stack or registers and this breaks any attempts to make simple OOP such way, of course if you don't know PB inside.
This all makes me tired and angry, cause I was counting on such a simple and nice realization to allow using classes. What a stupidity to waste all such realizations instead of add one of them to language and let it be for those who needs? Not anyone uses PB only to play with assembler and pointers at long winter nighs, damn.
That's all really sad cause I definitely need some very basic classes in my game (else I will drown in all the shit of procedures or ugly pseudo-oop in C style).