Assigning a function reference to a type field.

BlitzMax Forums/BlitzMax Programming/Assigning a function reference to a type field.

The r0nin(Posted 2005) [#1]
I was wondering if it was possible to set a field within a type that I can then associate with a function and call using that field. This would make my programs much more OOP, and solve a few problems I'm having. This is the kind of thing (in part) I'm trying to do:

Zone.bmx


Main.bmx
Strict
Include "Zone.bmx"

Graphics 800,600


Global c_Zone:t_zone = New t_zone
c_zone.Create(0,20,0,20,LabelIt())

While (Not KeyHit(KEY_ESCAPE))

Cls
t_zone.CheckZones()

Flip
Wend

Function LabelIt()
	DrawText ("Activated",MouseX(),MouseY())
End Function

The program compiles fine unless I try to reference the function in Check(). I'm guessing that I'm assigning a pointer to "on_rightclick" in the Create() Method, but I don't know how to call the function associated with it (unless I should be declaring on_rightclick as an object? Pointer? something else?)

Any help would be .... errr .... helpful.


The r0nin(Posted 2005) [#2]
Anyone know how this can be done (assigning a Function in a main file to a field in a imported file, so that I can call that function from a type)?


Sarge(Posted 2005) [#3]
..


gman(Posted 2005) [#4]
very handy feature. i used it quite a bit in the Irrlicht wrapper in order to bind C++ classes to BMAX types. here is an example. hopefully its clear enough...

Strict
Framework BRL.Blitz

Type testfuncstore
	Field testfunc3val:Int=2233445

	' function references are stored as byte ptrs
	Field func1:Byte Ptr
	Field func2:Byte Ptr
	Field func3:Byte Ptr
	
	Method dofunc1:Float()
		' this definition must match the function called and have the ()
		' after this line you can use locfunc1 just like a function
		Local locfunc1:Float()=func1
		Return locfunc1()
	EndMethod
	Method dofunc2(msg:String)
		' this definition must match the function called and have the ()
		' after this line you can use locfunc2 just like a function
		Local locfunc2(msg:String)=func2
		locfunc2(msg)
	EndMethod	
	Method dofunc3:Int()
		' this definition must match the function called and have the ()
		' after this line you can use locfunc3 just like a function
		Local locfunc3:Int(test:testfuncstore)=func3
		
		' pass this instance in
		Return locfunc3(Self)
	EndMethod
	
	Method getFunc3Val:Int()
		Return testfunc3val
	EndMethod
	
	' you can also point to Type functions since they are static
	' this is particularly interesting if you use this function as a C callback
	' function.  you can essentially call Methods on particular instances of BMAX types
	' from C.  it takes a little more setup than this shows but hopefully you get the idea.
	Function typefunc3:Int(test:testfuncstore)
		' you can call methods and reference fields on the particular instance passed in
		Return test.getFunc3Val()
	EndFunction
EndType

' test function with a return value
Function func1:Float()
	Return 1.111
EndFunction

' test function with a paramter
Function func2(msg:String)
	DebugLog(msg)
EndFunction

Local tester:testfuncstore=New testfuncstore

' store pointers to the functions
tester.func1=func1
tester.func2=func2
tester.func3=testfuncstore.typefunc3

' show the return value
DebugLog(tester.dofunc1())
' dofunc2 does the debuglog
tester.dofunc2("msg parameter passed")
' show the int return value from the type function
DebugLog(tester.dofunc3())


as for the import files, try moving the functions to a new file:

' testfuncs.bmx
Public

' test function with a return value
Function func1:Float()
Return 1.111
EndFunction

' test function with a paramter
Function func2(msg:String)
DebugLog(msg)
EndFunction

and then adding an import line at the top of the type file (remember to remove the functions from the first file. works the same as before.
Strict
Framework BRL.Blitz
Import "testfuncs.bmx"

Type testfuncstore
	Field testfunc3val:Int=2233445
	
	' function references are stored as byte ptrs
	Field func1:Byte Ptr
	Field func2:Byte Ptr
	Field func3:Byte Ptr
	
	Method dofunc1:Float()
		' this definition must match the function called and have the ()
		' after this line you can use locfunc1 just like a function
		Local locfunc1:Float()=func1
		Return locfunc1()
	EndMethod

	Method dofunc2(msg:String)
		' this definition must match the function called and have the ()
		' after this line you can use locfunc2 just like a function
		Local locfunc2(msg:String)=func2
		locfunc2(msg)
	EndMethod

	Method dofunc3:Int()
		' this definition must match the function called and have the ()
		' after this line you can use locfunc3 just like a function
		Local locfunc3:Int(test:testfuncstore)=func3
		
		' pass this instance in
		Return locfunc3(Self)
	EndMethod
	
	Method getFunc3Val:Int()
		Return testfunc3val
	EndMethod
	
	' you can also point to Type functions since they are static
	' this is particularly interesting if you use this function as a C callback
	' function.  you can essentially call Methods on particular instances of BMAX types
	' from C.  it takes a little more setup than this shows but hopefully you get the idea.
	Function typefunc3:Int(test:testfuncstore)
		' you can call methods and reference fields on the particular instance passed in
		Return test.getFunc3Val()
	EndFunction
EndType


Local tester:testfuncstore=New testfuncstore

' store pointers to the functions
tester.func1=func1
tester.func2=func2
tester.func3=testfuncstore.typefunc3

' show the return value
DebugLog(tester.dofunc1())
' dofunc2 does the debuglog
tester.dofunc2("msg parameter passed")
' show the int return value from the type function
DebugLog(tester.dofunc3())



The r0nin(Posted 2005) [#5]
Errr.... to a point. BTW, in the Method declaration for dofunc2(msg:String), did you mean for the line before the End Method to read "Return locfunc2(msg)" instead of just "locfunc2(msg)"?

Let me see if I have this straight. You declare fields of your type which act as byte pointers. You then associate each method with that pointer (I'm a little unclear on this. What purpose do the local variables like "locfun1:float()" serve? And where are they returned to?). Then after creating your instance of the type (tester) you point the field pointer in the type to each function (I assume that in "tester.func1=func1" the second func1 is the function defined outside the method ... I'm getting confused by the name overloading). Is that the basic process?

Note that what I want to do is create a method in a type that has no definition, then, in the main body of the program, assign that method to be a function that I have local to that main body. I think that is what you are doing, but I'm still fuzzy on it.

My main idea is this, I would like to code a type to hold mouse events in zones. This type would hold open references to the mouse being right-clicked, left-clicked, and so on. I can then use this file on many different programs, simply by making a function for each possible mouse state and connecting it to the open method in the type. That way in one program a right-click could maximize, while in another the right-click could select. All I would have to do is refer the type's method "right_click" to the function in each different program that it should activate. I think I see how to do it with your code, but the actual methods in your code that you overwrite have me confused.

Anyway, off to experiment with your code! Thatnks for the help!!


gman(Posted 2005) [#6]

did you mean for the line before the End Method to read "Return locfunc2(msg)


nope. the debuglog in there just prints the message. not a real functional thing i know.

You declare fields of your type which act as byte pointers


correct. or if you know the format of the function before hand you can define them directly. by doing this though you are forcing the definition of the stored function pointer. by just using a Byte Ptr you can change functions stored on a whim.
Field func1:Float()


You then associate each method with that pointer


you can only associate functions so keep that in mind. also, (you probably know this but) functions inside of types have no access to instance information such as field values.

What purpose do the local variables like "locfun1:float()" And where are they returned to?


its storing a pointer to a function definition so that you can call that function. you cannot call a function directly from a byte ptr (at least not that i have been able to accomplish). the variable is a local function pointer so it goes away at the end of the method call.

I assume that in "tester.func1=func1" the second func1 is the function defined outside the method... Is that the basic process?


correct. i have a honey-do to get done... will try to make myself more clear in a bit.


gman(Posted 2005) [#7]

My main idea is this, I would like to code a type to hold mouse events in zones. This type would hold open references to the mouse being right-clicked, left-clicked, and so on. I can then use this file on many different programs, simply by making a function for each possible mouse state and connecting it to the open method in the type. That way in one program a right-click could maximize, while in another the right-click could select. All I would have to do is refer the type's method "right_click" to the function in each different program that it should activate


it almost sounds like you would be better served with and abstract interface setup. let me see if i can whip an example up along those lines and see if it better suits what you are trying to accomplish... with better naming this time :)


The r0nin(Posted 2005) [#8]
Ok. I'm trying to do mine in a slightly different order (maybe that's what has me confused). Here's how I'm trying to do it (with just the important stuff reproduced):

In my main file I have a call to create the instance of the type, a function call to pass the pointer into the self-contained type, and the function to be passed:

Strict
Include "RZone.bmx"  'my zones file... the part I want to be modular 

Graphics 800,600


Global c_Zone:r_zone = New r_zone

c_zone.Create(0,20,0,20,LabelIT())  'sets the boundary of the zone and passes the function to initiate on a left-click

While (Not KeyHit(KEY_ESCAPE))

Cls
r_zone.CheckZones()  'checks to see if any zones are activated

Flip
Wend

Function LabelIt:Int()  'my function I want to use for a left-click
	DrawText ("Activated",MouseX(),MouseY())
End Function


Then, in my RZone.bmx (I'm trying to keep this segregated and modular) I have the type declaration, the method to create each instance of the type, the method to call the assumed function, and the code to check whether it's time for the assumed function to be called:
Type r_zone
	Field x1#,x2#  'x location of the zone and its width
	Field y1#,y2#  'y location and width
	Field zone_number  'used for indexing
	Field left_clicked:Byte Ptr  'the pointer to use for the assumed function

	Global r_ZoneList:TList  'for my list/cycle

'********************** Create Zone *****************

	Method Create (a1#,a2#,b1#,b2#,fun1:Byte Ptr)  'gets the zone dimensions and the function to call on a left-click
			Local temp_zone:r_zone = New r_zone  'temp instance to use for setup
			temp_zone.x1 = a1
			temp_zone.x2 = a2
			temp_zone.y1 = b1
			temp_zone.y2 = b2
			temp_zone.left_clicked = fun1  'this should get the pointer to the function for left-click!
			If r_ZoneList = Null Then r_ZoneList = CreateList()
			r_ZoneList.AddLast(temp_zone)

	End Method

'********************** Check Zones ******************

	Function CheckZones()  'I send this into a method so that it is protected from outside influences 
		For Local test:r_zone = EachIn r_ZoneList
			test.Check(MouseX(),MouseY(),MouseHit(1))
		Next
	End Function	
	
'********************** Check ************************

	Method Check(a#,b#,but1)  'My method to check if the mouse is in the zone and the left button down
		If a>= x1 And a<= x2 And b>= y1 And b<= y2 Then
			If but1 >0 Then If_leftclicked()
		End If
	End Method

'********************Left Click*********************
	
	Method If_Leftclicked:Int()
	Local locfun1:Int() = left_clicked
	End Method

End Type

My problem comes when I try to pass the pointer from the main file through a parameter in the Create() Method. Is there any way to do that?

Thanks again for all your help. Get back to me at your leisure... I would hate to be the cause of any domestic infelicity! *grin*


gman(Posted 2005) [#9]
to fix your problem, dont include the () when passing the function. only the name:
c_zone.Create(0,20,0,20,LabelIt) 

also, in If_Leftclicked() you need to call the local version of the function:
Method If_Leftclicked:Int()
Local locfun1:Int() = left_clicked
Return locfun1()
End Method

in addition, if your left_clicked function is always going to take the same parameter types and return the same value type you could simply specify the definition it in the field. so your field then becomes:
Field left_clicked:Int()

and then If_Leftclicked changes to:
Method If_Leftclicked:Int()
Return left_clicked()
End Method

with this, you could even eliminate If_Leftclicked altogether since Check() could be changed to call left_clicked() instead of If_Leftclicked().

well... since i typed it all up heres an example of an interface implementation :) i think also that you may consider this over the single function pointer once you start adding more mouse functionality due to the abilility to wrap the mouse functionality up into a single type for storage on the zone type.
' imousehandler.bmx
Strict
Public

' abstract types cannot be instantiated directly and are meant to define an interface so other routines know what to expect.
' the only problem with this is that there is no way to change what parameters and return types the methods have.  using function
' pointers you could.
Type IMouseHandler Abstract
	' by not marking the method as abstract means you dont have to override it in the subtype
	Method right_click()
		DebugLog("IMouseHandler.right_click()")
	EndMethod
	
	' we must override this one in the subtype
	Method left_click() Abstract
EndType

' zone1.bmx
Strict
Import "imousehandler.bmx"

Public

Type Zone1MouseHandler Extends IMouseHandler
	' there is no default and must define this method
	Method left_click()
		DebugLog("Zone1MouseHandler.left_click()")
	EndMethod
EndType

Type Zone1
	Method run()
		Local mouse:IMouseHandler=New Zone1MouseHandler
		mouse.right_click()
		mouse.left_click()
	EndMethod

	Method runWithHandler(mouse:IMouseHandler)
		mouse.right_click()
		mouse.left_click()
	EndMethod
EndType

' zone2.bmx
Strict
Import "imousehandler.bmx"

Public


Type Zone2MouseHandler Extends IMouseHandler
	' this one does something other than the default
	Method right_click()
		DebugLog("Zone2MouseHandler.right_click()")
		Super.right_click()
	EndMethod
	' there is no default and must define this method
	Method left_click()
		DebugLog("Zone2MouseHandler.left_click()")
	EndMethod
EndType

Type Zone2
	Method run()
		Local mouse:IMouseHandler=New Zone2MouseHandler
		mouse.right_click()
		mouse.left_click()
	EndMethod
EndType

' mousestuff.bmx
Strict
Framework BRL.Blitz

Import "imousehandler.bmx"
Import "zone1.bmx"
Import "zone2.bmx"

Local zone1obj:Zone1=New Zone1
Local zone2obj:Zone2=New Zone2

' both zones expect the same interface from the mouse but the individual implementations
' of the mouse handler are different for each zone

DebugLog("")
DebugLog("-- zone1 run()")
zone1obj.run()
DebugLog("")
DebugLog("-- zone2 run()")
zone2obj.run()

Local mouse1handler:Zone1MouseHandler=New Zone1MouseHandler
Local mouse2handler:Zone2MouseHandler=New Zone2MouseHandler

' now another cool thing is using an interface type as a parameter it doesnt matter the specifics of the mousehandler, 
' we just pass in the one we want it to use (you could store it to a field of type IMouseHandler as well)	
DebugLog("")
DebugLog("-- zone1 runWithHandler() mouse1handler")
zone1obj.runWithHandler(mouse1handler)
DebugLog("")
DebugLog("-- zone1 runWithHandler() mouse2handler")
zone1obj.runWithHandler(mouse2handler)

hope im making sense here. im not the best communicator at times :(


The r0nin(Posted 2005) [#10]
 to fix your problem, dont include the () when passing the function. only the name:

Hehehehe, yeah, I figured that out after a little experimentation. I missed the call to the local version in the method, though. Once I changed that the way you suggested, it works like a charm!

That interface implementation is awesome. Both of these coding strategies will make a big difference in what I am working on (LOL! Seems like every time I get halfway through a project, I find a way to do it better). Thanks massively!!!! You're getting a featured spot in the game I'm working on (either as a NPC general to help out the player... or maybe even as a boss monster...heheheh), plus a thank you in the credits (and, since I've promised it to you, it's more motivation to finish it!).

Once again, thanks, I've learned a ton!