Function Pointers in game logic
Hello everyone and welcome to my discussion on Function Pointers.
Function Pointers used in game code can be a very useful technique for speeding up loop
mechanisms execution time. They can do this by avoiding decision structured execution in
game logic allowing for less instructions.
In the WNDCLASSEX STRUCT that is passed as a pointer in RegisterClassEx, the .lpfnWndProc
value is an example of a function pointer. This value points to your Window Procedure that
is used by Windows.
Although function pointer are a simple concept they do lead to complex code.
Here is how function pointer work.
lda MACRO Operand, Param ;; lda => load AddressOf
lea eax, Param
mov Operand, eax
ENDM
;===============================================================>
.data
swFunctionPointers db "Function Pointers", 0
.code
SomeFunction00 PROC
invoke MessageBoxA, null, CSTR("In Function 00"), addr swFunctionPointers, MB_OK
ret
SomeFunction00 ENDP
SomeFunction01 PROC
invoke MessageBoxA, null, CSTR("In Function 01"), addr swFunctionPointers, MB_OK
ret
SomeFunction01 ENDP
SomeFunction03 PROC
invoke MessageBoxA, null, CSTR("In Function 02"), addr swFunctionPointers, MB_OK
ret
SomeFunction03 ENDP
etc.
;===============================================================>
.data
g_pFunction DWORD 6 dup (0)
.code
SomeProc PROC
local i:dword
...
lda g_pFunction[0*4], SomeFunction00
lda g_pFunction[1*4], SomeFunction01
lda g_pFunction[2*4], SomeFunction02
lda g_pFunction[3*4], SomeFunction03
lda g_pFunction[4*4], SomeFunction04
lda g_pFunction[5*4], SomeFunction05
mov i, 0
.while (i < 6)
mov ecx, i
mov eax, 0
.if (eax != g_pFunction[ecx*4])
call g_pFunction[ecx*4]
.endif
inc i
.endw
...
ret
SomeProc ENDP
;===============================================================>
In the zip file attachment is an asm file containing MASM32 code that demonstrates
how function pointers can be used to speed up loop execution.
Or just make the table:.data
g_pFunction dd offset SomeFunction00
dd offset SomeFunction01
...
dd offset SomeFunction05
Or just inline your functions... :idea:
What do you mean by decision structured execution? If you mean that this will avoid doing comparisons and jumps you should watch what .while really do. :idea:
Just plain and simple, Jump Tables.
.data
RoutineJumpTable dd offset Routine_0, offset Routine_1, offset Routine_2, ........... etc.
.code
mov eax,0 ;routine_number
jmp [RoutineJumpTable+eax*4]
; call [RoutineJumpTable+eax*4] ; or call a routine
align 4
Routine_0:
; start of your awesome routine code
ret
align 4
Routine_1:
; start of your awesome routine code
ret
align 4
Routine_2:
; start of your awesome routine code
ret
Hello HSE.
Thank you very much for your knowledgeable advice.
Hello Siekmanski.
Thank you very much also for your knowledgeable advice.
Hello felipe.
Thank you very much for your input.
Yes decision structured execution == comparisons and jumps.
Yes the aim is to avoid doing comparisons and jumps, very much so.
Quote from: Caché GB on June 22, 2018, 12:01:12 PM
Yes the aim is to avoid doing comparisons and jumps, very much so.
I'm not pretty sure, but i think the mmx instruction set has some "feature" to avoid this when making some computations. Someday i will take a look to mmx instruction set again but deeper. Maybe (probably) other members knows about this feature. Maybe there are similar features in newer instruction sets. :idea:
The main problem with this feature is that you can use it just for specific computation types, but as i said before i really don't know, i just remember a little what i readed some long time ago about mmx. :idea:
I think for finite state machine,its great to have a jumptable,just make a loop that loads array of State for 100's of enemies and use jumptable
also useful for the right image for 2d sprite for enemies/player character,depending on direction it points at and moves,action like fire a gun,jump,nonaction idle I seen in some games,the PC falls asleep after certain milliseconds inaction
Thanks felipe for the info.
Once again thanks to HSE and Siekmanski. Jump Tables are definitely the goto.
I see that hierarchical complexity can quickly be implemented and for deep nesting, it is possible to have
predetermined or calculated code paths (tree transversal) for rapidly dealing with course and effect. As
daydreamer pointed out "loads array of STATE for 100's of ... and use jump table".
Thanks daydreamer.
Awesome! Welcome to the JUNGLE, Table.
.data
szEntityZero db "I am Entity Zero", 0
szEntityOne db "I am Entity One", 0
etc.
g_pBehaviour dd offset Behaviour00, offset Behaviour00Routine00, offset Behaviour00Routine01, offset Behaviour00Routine02
dd offset Behaviour01, offset Behaviour01Routine00, offset Behaviour01Routine01, offset Behaviour01Routine02
dd offset Behaviour02, offset Behaviour02Routine00, offset Behaviour02Routine01, offset Behaviour02Routine02
dd offset Behaviour03, offset Behaviour03Routine00, offset Behaviour03Routine01, offset Behaviour03Routine02
dd offset Behaviour04, offset Behaviour04Routine00, offset Behaviour04Routine01, offset Behaviour04Routine02
dd offset Behaviour05, offset Behaviour05Routine00, offset Behaviour05Routine01, offset Behaviour05Routine02
GameEntity ENTITY NumEntities dup (<>)
.code
;===========================================>
...
lea esi, GameEntity
assume esi:ptr ENTITY
mov [esi].ENTITY.MyName, offset szEntityZero
add esi, sizeof(ENTITY)
mov [esi].ENTITY.MyName, offset szEntityOne
add esi, sizeof(ENTITY)
etc.
mov i, 0
.while (i < NumEntities)
invoke RAND32, 6 ;; RNG by NaN (May 5, 2001)
imul eax, 4
mov edx, eax
mAm [esi].ENTITY.BehaviourPtr, [g_pBehaviour+eax*4]
invoke RAND32, 3
add eax, 1
add eax, edx
mAm [esi].ENTITY.RoutinePtr, [g_pBehaviour+eax*4]
add esi, sizeof(ENTITY)
inc i
.endw
assume esi:
...
;===========================================>
...
lea esi, GameEntity
assume esi:ptr ENTITY
mov i, 0
.while (i < NumEntities)
push esi
call [esi].ENTITY.BehaviourPtr
add esi, sizeof(ENTITY)
inc i
.endw
assume esi:nothing
...
;===========================================>
Behaviour00 PROC Who:PTR ENTITY
mov edx, Who
assume edx:ptr ENTITY
invoke MessageBoxA, null, CSTR("Acting out Behaviour 0"), [edx].ENTITY.MyName, MB_OK
mov edx, Who
call [edx].ENTITY.RoutinePtr
assume edx:nothing
ret
Behaviour00 ENDP
Behaviour00Routine00 PROC
invoke MessageBoxA, null, CSTR("while doing 0s Routine 0"), [edx].ENTITY.MyName, MB_OK
ret
Behaviour00Routine00 ENDP
Behaviour00Routine01 PROC
invoke MessageBoxA, null, CSTR("while doing 0s Routine 1"), [edx].ENTITY.MyName, MB_OK
ret
Behaviour00Routine01 ENDP
Behaviour00Routine02 PROC
invoke MessageBoxA, null, CSTR("while doing 0s Routine 2"), [edx].ENTITY.MyName, MB_OK
ret
Behaviour00Routine02 ENDP
etc.
;==================================================================>
hello Caché GB;
Maybe you can gain some cycles if you do a mov and a jump; I think is more viable/quickly than a call/ret pairs.
Something like:
lea eax,[g_pBehaviour+??*4] ;*4 because dwords addresses
jmp eax
align 4
Behaviour00:
...
jmp go_back_to_loop
go_back_to_loop is better if aligned to a multiple. (align 4,8,...)
The way to go is using tables, we often do this while on eletronics, only logic tables (or pre calculated tables) that are translated to logic circuits (diodes, transistors, ...).
it would be nice to start with a simple solution,for example 2D topdown game for 100+ enemies and call simple PROC's for turning and moving that are coded oldschool style with trigo and simple 2D physics
and later combine with this:
https://docs.microsoft.com/en-us/windows/desktop/direct3d9/efficiently-drawing-multiple-instances-of-geometry (https://docs.microsoft.com/en-us/windows/desktop/direct3d9/efficiently-drawing-multiple-instances-of-geometry)
,changing your PROC for turning enemy alpha degrees,in pitch,roll,jaw angles and moving x,y,z is exchanged with wrapper for matrices code that do the same with instances
in a RTS game,you can have different colors for the two teams competing
this is because the alternative is probably fun to code SIMD that take 100+lowpoly 3dobjects and copy,3dtranslate/3drotate before sending to vertexbuffer ,but too much overhead so it lags and too much limitations for great graphics
Hello mineiro.
"mov and a jump" although great for gaining some cycles, I don't see it possible to be used when called
from different parts of a program. " call/ret pairs" need to be implemented to return to point of call.
However thank you for your suggestion.
Hello daydreamer.
Nice read there. Thank you
In DX11.1 the Compute Shader is perfect for GPGPU programming. It is different from the other pipeline stages
because it works beside them. That is it does not explicitly have an input or output parameter for the previous
or next stage. Compute Shader is not limited to graphical applications. Algorithms for physics, animation, AI,
compression and cryptography or crypto mining can be implemented with Compute Shaders. It is awesome
for thread group processing.