Method Pointers

BlitzMax Forums/BlitzMax Programming/Method Pointers

_Skully(Posted 2009) [#1]
Is there a way to do method pointers?

Looking over my TMParticleFX system, I have a sequencer system that attaches to a particle and influences its behavior... eg:


Type Particle
   Field X:int
   Field Y:int
   Field dX:TMSequencer=null
   Field dY:TMSequencer=null

   Method Update()
      If dX then X=dX.Value()
      If dY then Y=dY.Value()
   End Method
End Type


Now I understand the cost of doing business here... expand the above into most Particle parameters and you have a multitude of null checks per particle which adds up. Being the optimizing kind of guy I am, I thought about a better way of doing this but I havent used Function pointers yet... and worse, I would actually need a method pointer to update an active instance. What I would want is for the sequencer to call a method directly when its updated.

Is it possible?


beanage(Posted 2009) [#2]
AFAIK, there are no method pointers. I think overriding methods/ abstract methods is the closest pseudo-approximation of method-pointer behaviour.. so what I would do is:

Type TMParticle

	Field x:Int
	Field y:Int
	Field dX:TMSequencer = New TMNullSequencer
	Field dy:TMSequencer = New TMFancySequencer
	
	Method Update()
		dX.Operate X
		dY.Operate Y
	End Method
	
End Type

Type TMSequencer Abstract
	Method Operate( variable:Int Var ) Abstract 'you may want to change this 'int var' to a byte/int pointer
End Type

Type TMNullSequencer Extends TMSequencer

	Method Operate( variable:Int Var )
		'intentionally left empty
	End Method

End Type

Type TMFancySequencer Extends TMSequencer

	Method Operate( variable:Int Var )
		'some cool stuff here
		'...
	End Method

End Type


Not sure, but this might be a smart way to solve it..


_JIM(Posted 2009) [#3]
@_Skully: Not sure if this is the stuff you should optimize first. From your example, you're using a TParticle class, that means in the best case scenario, you are using an array of particles. It's ok, but this can be done better.

Instead of having a generator hold an array of particles, have it hold arrays of particle properties:

Type TParticleGenerator
    Field X:Float[]
    Field Y:Float[]

   'etc.
EndType


The advantage would be that if the compiler is smart enough, it could optimize it for SSE. Not to mention the fact that you could do selective updates (only position, etc.) a lot faster.

Regarding your problem, it could be solved here by kind of reverse using those sequencers. What I mean is assign "Float Ptr"s to sequencers by picking them up from the arrays. That way, the sequencers update only what they need to. The rest remains unchanged.

A further optimization would be to group your arrays for vertex array usage:

Type TParticleGenerator
    Field Positions:Float[]
    Field Colors:Float[]
    Field UVs:Float[]

   'etc.
EndType


At the end you'll only have to point to those arrays and call a glDrawArrays.

Hope this helps ;)


_Skully(Posted 2009) [#4]
Great idea but it still misses the clock cycle savings I'm looking for :)

Actually.. couldn't I pass the Sequencer a memory address for the variable itself and update that directly? Hmmmm


beanage(Posted 2009) [#5]
@_JIM: Uh, thats a good idea! May there be a synthesis btwn mine and yours e.g.:

Type TMParticle

	Global positions:Int[] 'actual data in these arrays
	Global colors:Int[]
	Global uvs:Float[]
	'---
	'just pointers here
	Field x:Int Ptr
	Field y:Int Ptr
	Field dX:TMSequencer = New TMNullSequencer
	Field dy:TMSequencer = New TMFancySequencer
	
	Method Update()
		dX.Operate X
		dY.Operate Y
	End Method
	
	Method New()
		positions = positions[..positions.length+2]
'		colors = ..
'		uvs = ..
		x = Int Ptr Varptr positions[positions.length-2]
		y = Int Ptr Varptr positions[positions.length-1]
	End Method
	
End Type

Type TMSequencer Abstract
	Method Operate( variable:Int Ptr ) Abstract
End Type

Type TMNullSequencer Extends TMSequencer
	Method Operate( variable:Int Ptr )
		'intentionally left empty
	End Method

End Type

Type TMFancySequencer Extends TMSequencer
	Method Operate( variable:Int Ptr )
		'some cool stuff here
		'...
	End Method
End Type



_Skully(Posted 2009) [#6]
Hmmm.. I Don't think thats going to work either because particles can be staged with all their sub-components and then spawned as new objects... the new objects would end up copying the original variable pointer... Damn! Got to figure this out... it would save so many tests when the particle count gets way up there.


beanage(Posted 2009) [#7]
still misses the clock cycle savings I'm looking for

You are looking for skipping the 'If sequencer Then' part, right? So the abstract-method thing reduces it at least to an affectless stack entry.

Actually.. couldn't I pass the Sequencer a memory address for the variable itself and update that directly?

Ya isnt that exactly what my/_JIMs above code does?


I Don't think thats going to work either because particles can be staged with all their sub-components and then spawned as new objects... the new objects would end up copying the original variable pointer... Damn!


I got the feeling I missed some of that part.. could you post a pseudo-solution pretending bmax would support method pointers?


_Skully(Posted 2009) [#8]
Your posts came in while I was posting... just reading now.


_Skully(Posted 2009) [#9]
I don't know why I posted int's LOL.. fixed here..

I haven't used Function Pointers yet so excuse me if this psuedocode is a little poochy

Type Particle
   Field X:Float
   Field Y:Float
   Field VX:Float
   Field VY:Float

   Method Update()
      X:+VX
      Y:+VY
   End Method

   Method UpdateVX(V:float)
      VX=V
   End Method
End Type

Type TMSequencer
   Global List:Tlist=new Tlist
   Field MethodPtr(a:Float)
   Field Value:Float

   Method New()
      List.AddLast(Self)
   End Method

   Function Create:TMSequencer(Func())
     Local S:TMSequencer=New TMSequencer
     S.MethodPtr=Func
     S.Value=5
   End Function

   Method Update()
      MethodPtr(Value)
   End Method
End Type

Part:TMParticle=New TMParticle
Part.dX=TMSequencer.Create(Part.UpdateVX)

For s:TMSequencer=Eachin TMSequencer.List
   S.Update()
Next
Part.Update()


This way, dynamicly sequenced Fields would only use CPU time if they actually exist... no requirement for null checks etc.


beanage(Posted 2009) [#10]
I FINALLY got what you want to do.. unfortunately it seems (to me) as ingenious as impossible (doesnt mean there's not a good workaround [in love with that word :]), but let me summarize what I understood to be sure..
- you want to assign a method from a type A to a "method pointer" in a remote type B
- you ain't using a function pointer, because you want to directly update fields of type A
- you'd assume, that the "method pointer" would operate on .. WAITWAITWAIT.. When I first read your code, I thought the executed "method pointer" would be operating on all particles (is there an alternative?).. but this is not mutually-exclusive as soon as there's a second sequencer.. with a different particle::method that equally operates on all instances.. are you aware of that? am i missing something?

[edit:] At some point I still think my abstract types solution is the best..


_Skully(Posted 2009) [#11]
as ingenious as impossible

There's my smile for the day 8)

- you want to assign a method from a type A to a "method pointer" in a remote type B

Correct

- you ain't using a function pointer, because you want to directly update fields of type A

Update the field of the instance yes, but I also "Spawn" new Particles from an existing particle template pool so variable pointers wouldn't work since the Sequencer wouldn't know what variable it is actually attached to.. I suppose I could store that and have a Select / Case scenaro..hmmm


- you'd assume, that the "method pointer" would operate on .. WAITWAITWAIT.. When I first read your code, I thought the executed method pointer would be operating on all particles.. but this is not mutually-exclusive as soon as there's a second sequencer.. with a different particle::method that equally operates on all instances.. are you aware of that? am i missing something?


Not sure I really understand that LOL.. but it needs to operate on only one instance not all... different particles different needs ;)


N(Posted 2009) [#12]
You can do method pointers with libffi. It's not exactly pretty, but it does work.


_Skully(Posted 2009) [#13]
Can you point me to the method involved in using that?

http://sourceware.org/libffi/

Doesn't list Windows at all... Linux, OSX, FreeBSD etc... but no Windows


beanage(Posted 2009) [#14]
[edit:] Sry, I misunderstood the Method pointer just operates on that particle. Thats why you pass it ParticleInstance.Method and not ParticleType.Method.


N(Posted 2009) [#15]
Pretty sure it works on Windows, seeing as how I did a bunch of testing using libffi under Windows. Might need cygwin or msys for it, though.


_Skully(Posted 2009) [#16]
BeAnAge,

I suppose this entirely depends on Marks implementation of Methods. If the code for the Method is actually included in the instance then there is a specific memory address for that method associated with that instance in which case all Mark would have to do to implement Method pointers is provide a mechanism to return the memory address.

If, however, the Method itself is housed within the base class definition and it just operates on values offset by the instance then I'm hooped. In that case, it would be nice to be able to tell the compiler to copy the code (aka Inline) to the instance itself... but that, even to me, sounds like it would be no easy task to implement.

... interuption... but thats good. Because I just realized that IF the libffi library that Niluim pointed to works then the methods MUST be part of the instance and not just the base class definition... right?

Nilium,
I'll look into that later.. thanks for that!


beanage(Posted 2009) [#17]
Now I understand why
Varptr Self
is not working.. <Self> is indeed not a variable! What an extension of horizon LOL


N(Posted 2009) [#18]
What exactly do you want a varptr to Self for?


_Skully(Posted 2009) [#19]
Thats why you pass it ParticleInstance.Method and not ParticleType.Method


You can't call a method assocated with a Type until its instanced.. otherwise there is no data to operate on.

Nilium,
What exactly do you want a varptr to Self for?

Self improvement? I know.. I know.. ;)


N(Posted 2009) [#20]
You can't call a method assocated with a Type until its instanced.. otherwise there is no data to operate on.
You can, but yeah, if the method tries to work on an instance at all, it will crash.


_Skully(Posted 2009) [#21]
That would just be Method Abuse if it didn't operate on the instance wouldn't it? I think it would have identity issues.. I'm a function not a method!


Czar Flavius(Posted 2009) [#22]
I posted a question about method pointers a few months ago, although I couldn't find it in the search unfortunately. From what I could gather a Blitz method is just a function in disguise with a hidden first parameter which is the object itself (aka Self). So instead of a method pointer, just use a function pointer and pass as the first parameter the object itself in question. Shouldn't this give practically the same functionality?

Type bob
	Field a = 10
	Field funcptr:Int(me:bob, z:Int)
End Type

Function hi:Int(me:bob, z:Int)
	Return me.a + z
End Function

Local y:bob = New bob
y.funcptr = hi
Print y.funcptr(y, 2)
Not pretty, but it works. You could perhaps wrap it up like so.
Type bob
	Field a = 10
	Field funcptr:Int(me:bob, z:Int)
	Method hi:Int(z:Int)
		Return funcptr(Self, z)
	End Method
End Type

Function hi:Int(me:bob, z:Int)
	Return me.a + z
End Function

Local y:bob = New bob
y.funcptr = hi
Print y.hi(2)



plash(Posted 2009) [#23]
That would just be Method Abuse if it didn't operate on the instance wouldn't it? I think it would have identity issues.. I'm a function not a method!
Technically speaking, a method is a function - it just has an extra argument.


_JIM(Posted 2009) [#24]
Sorry for the late answer, took a while to get home.

I forgot about adding/removing particles, heh. What you could do is have your particle pool as an array. You could start with a size that fits most applications, then double it each time you need more particles. Say you start with 128. If you need 129, make the array 256. If you then need 257, make it 512. That way you'll have 4-5 reallocations of the array in the entire runtime of the application.

Once you got that set up, you can make a second array that could be encoded in bits (since you need just a boolean flag). So for each int in the array, you could remember the state of 32 particles (active or not).

That way when you remove a particle, you flag it as inactive. All other particles get to keep their pointers.

What I was referring to earlier about sequencers was that you can create a sequencer, give it a rule (linear interpolation, sine wave, etc.) and a Float Ptr that points somewhere in one of the arrays inside the generator. That way you ONLY update sequencers that actually exist. There's also less memory waste, as there are just as many sequencers defined as there are active (you no longer hold another field - pointer to sequencer - for every field in your particle).

As for the final assembly of the vertex arrays... it's not going to be as fast as I presumed in my first post. Instead of just pointing to that array, you'll have to build a new one based on the active particles. You should probably check and MemCopy as many particles at once as possible. Check for consecutive particles flagged as "active" and bulk-copy them to the final vertex array.

I'm really sorry that I don't have some code to show you right now. Currently my particle engine is going through a rewrite and will probably end up as I just wrote above. I am curious myself to find out just how fast it can be.
I wrote something similar to this (a ribbon/trail system) and got to 1700-2200 fps with 10000 points on the trail. I seem to have hit the bottleneck of the GPU as the CPU never went above 70% while my early tests kept it at 100%. The trick is that I am doing 2 MemCopys and process just 1 node (2 vertices) per frame.

That's probably a bit off-topic so I'll end this post here :)


_Skully(Posted 2009) [#25]
The code I posted above is a rediculously simplified version of what I actually have going on... I already have array based template, holding, and active pools for particles, emitters, and Sequencers to speed things up... I start at 5000 since memory is really not much of an issue these days and it prevents jitter during large initial particle floods.

The sequencers are working great... I'm just trying to optimize their use so that in high volume moments there is very little overhead.

Time to go experiment with the examples above :)