Part of my code is running twice, how?

Blitz3D Forums/Blitz3D Programming/Part of my code is running twice, how?

Crinkle(Posted 2011) [#1]
Hello there,

I have a simple-ish program which is basically a start on a menu system.

However part of the code is running twice and creating double the amount of graphical buttons. I made an error effect to stop the code, with a counter for the number of "error stops" which increases every time there is an error.

Whilst the error portion runs twice, it only counts to one, but it does indeed run twice. additionally, the counter is only changed by adding to it each time the error routine runs, and is a global.

Why then is it both running twice, and denying its running twice?


 
Global settings_file$="setup.txt"
SeedRnd MilliSecs()

Graphics 1024,768,32,2
SetBuffer BackBuffer()

init_button_list()

Global menu_setup=0
Global menu$="MAIN_MENU"
Global mouse_timeout=0
Global ERROR_COUNT=0
Global xpos
; pre setup stuff	


 init_button_list()


;main stuff

While KeyDown(1)=0
	If menu_setup=0
		init_menu() ; load all menu bits
	EndIf

	draw_mouse()
	draw_buttons()
	
	buts=0
	For bl.blist=Each blist
		buts=buts+1
	Next
	Color 255,255,255
	Text 20,20,buts
	
	Flip
	Cls	

Wend


;functions

Function draw_mouse()
	Color 255,255,255
	Rect MouseX(),MouseY(),5,5
End Function

Type but
	Field img,click_cmd
End Type

Function draw_buttons()
	If menu="MAIN_MENU" Then
		xpos=160
		For b.but = Each but
			DrawImage b\img,GraphicsWidth()/2,xpos							
			xpos=xpos+100
		Next
	EndIf
End Function

Function errorpoint()
	ERROR_COUNT=ERROR_COUNT+1	
	For t = 1 To 100
		If Rnd(1,100)>85 Then Color Rnd(255,155),Rnd(255,155),Rnd(255,155)
		Rect 0+t,0+t,GraphicsWidth(),GraphicsHeight(),0
	Next	
	Color 0,0,0
	Text 20,20,"BREAKPOINT REACHED! TIME="+MilliSecs()	
	Text 20,35,"WE HAVE REACHED THE POINT YOU WANTED ME TO STOP AND TELL YOU WE HAD REACHED.
	Text 20+1,35,"WE HAVE REACHED THE POINT YOU WANTED ME TO STOP AND TELL YOU WE HAD REACHED.
	Text 20,50,"RESUMING IN 5 SECONDS..."
	Text 20+1,50,"RESUMING IN 5 SECONDS..."
	Color 0,0,0
	Rect 10,72,200,22
	Color 255,255,255
	Text 20,75,"ERROR NUMBER="+ERROR_COUNT

	Flip
	Delay 3000
End Function

Function gen_menu_but(txt$,action$,colour$)
	b.but = New but
	tempimage=CreateImage(Len(txt$)*12,16,2) 
	frame=0
	SetBuffer ImageBuffer(tempimage,frame)
	Color 0,0,0
	Rect 0,0,1024,1024,1
	Color 155,155,155
	Rect 0,0,ImageWidth(tempimage)-1,ImageHeight(tempimage)-1,0
	getcolour(colour$)
	Text ImageWidth(tempimage)/2,ImageHeight(tempimage)/2,txt$,1,1
	frame=1
	SetBuffer ImageBuffer(tempimage,frame)
	Color 200,150,75
	Rect 0,0,1024,1024,1
	Color 155,155,155
	Rect 0,0,ImageWidth(tempimage)-1,ImageHeight(tempimage)-1,0
	getcolour(colour$)
	Text ImageWidth(tempimage)/2,ImageHeight(tempimage)/2,txt$,1,1
	MidHandle tempimage
	b\img=CopyImage(tempimage)
	b\click_cmd=action$
	FreeImage tempimage
	
End Function

Function init_menu()
	For b.but = Each but ; clear old menu's buttons
		FreeImage b\img
		Delete b
	Next	
	For bl.blist = Each blist ; scan button list for this menu's buttons
		If bl\menu$=menu$ Then
			gen_menu_but(bl\txt$,bl\action,bl\colour)
		EndIf
	Next
	menu_setup=1
End Function

Type BLIST
    Field txt$,menu$,action$,colour$
End Type

Function init_button_list()

new_blist("THIS IS THE GO BUTTON","MAIN_MENU","menu EDIT_MAIN","yellow")

End Function

Function new_blist(txt$,menu$,action$,colour$)
	bl.blist = New blist
	bl\txt$=txt$
	bl\menu$=menu$
	bl\action$=action$
	bl\colour$=colour$
	errorpoint()
End Function


Function getcolour(colour$)
	If colour$="red" Then Color 255,0,0
	If colour$="green" Then Color 0,255,0
	If colour$="blue" Then Color 0,0,255
	If colour$="yellow" Then Color 255,255,0
	If colour$="purple" Then Color 255,0,255
	If colour$="cyan" Then Color 0,255,255
	If colour$="grey" Then Color 155,155,155
End Function




Yasha(Posted 2011) [#2]
You call init_button_list() in two places (I assume this is intentional?). Note that you call it the first time above the point where the global variables are declared and initialised. Since the global ERROR_COUNT is "initialised" to zero, this means it is being reset to zero after the first time errorpoint() modifies it.

This is an odd consequence of the fact that Blitz3D both allows block-scoped functions (which can usually see all global declarations outside them at all times) and a linear "outer level" that the program tries to run in-sequence - global "initialisation" is actually just any old assignment statement, that also happens to be on the same line that declares the variable.

If you want your globals initialised in the "outer level", you should make sure it happens ahead of all other code, to avoid this confusion. You could also note that Blitz3D automatically initialises all variables to zero or Null on entering their scope (unlike, say, C), so in many cases you can just drop the assignment altogether.

Last edited 2011


Crinkle(Posted 2011) [#3]
It's always the simple things that stump me. Why did i not see that!


Crinkle(Posted 2011) [#4]
I dont suppose anyone can solve the terrible framerate issue can they? I'm only getting about 5 frames a second for some reason? (after removing all delay commans)


Axel Wheeler(Posted 2011) [#5]
(Update: see end of post)

Well, I've narrowed it down, but I still don't understand why this happens...

These lines cause the slowdown:

	If menu_setup=0
		init_menu() ; load all menu bits
	EndIf


Commenting these three lines solves the slowness problem.

However, since the test in the first line returns false every frame after the first frame, it takes essentially zero time. And indeed, a time counter placed around the lines shows it takes 0 millisecs, 1 at most.

Nevertheless, commenting these lines speeds things to normal. So, creating the menu itself is not slow, but it somehow triggers the subsequent slowness each frame, even though everything else in the main loop is also fast.

I realize this makes no sense. Something's very odd here. millisecs from Flip() to Flip() (i.e. between flips) is basically zero. And Flip takes the correct amount of time, 16-17 ms at 60fps. This means that nothing in the main loop is unusually slow.

Nevertheless, screen updates are way slower. The screen update is called every frame and just puts a rect at mousex, mousey, and runs predictably fast.

So, the main loop runs at 60 fps, but the screen visibly updates at more like 10 fps. How is that possible?

The only thing I can figure is that mousex() and mousey() are returning old data for up to 1/10 sec for some reason.

UPDATE: Ok, got it I think. You need a SetBuffer(BackBuffer()) at the end of gen_menu_but(). I put one in and it speeds things back to normal. What doesn't make sense now is why it was writing to the screen at all, given SetBuffer() was left on the last button image created. Any ideas why this might happen?


Adam Novagen(Posted 2011) [#6]
Alright Crinkle. Have a look at your button creation code here.

Function gen_menu_but(txt$,action$,colour$)
	b.but = New but
	tempimage=CreateImage(Len(txt$)*12,16,2) 
	frame=0
	SetBuffer ImageBuffer(tempimage,frame)
	Color 0,0,0
	Rect 0,0,1024,1024,1
	Color 155,155,155
	Rect 0,0,ImageWidth(tempimage)-1,ImageHeight(tempimage)-1,0
	getcolour(colour$)
	Text ImageWidth(tempimage)/2,ImageHeight(tempimage)/2,txt$,1,1
	frame=1
	SetBuffer ImageBuffer(tempimage,frame)
	Color 200,150,75
	Rect 0,0,1024,1024,1
	Color 155,155,155
	Rect 0,0,ImageWidth(tempimage)-1,ImageHeight(tempimage)-1,0
	getcolour(colour$)
	Text ImageWidth(tempimage)/2,ImageHeight(tempimage)/2,txt$,1,1
	MidHandle tempimage
	b\img=CopyImage(tempimage)
	b\click_cmd=action$
	FreeImage tempimage
	
End Function
First, you create a temporary image. This is actually unnecessary; you can simply create b\img right off the bat and work on that, there's no need to create a temporary image, make a copy, and then delete the original.

Next, you grab the first frame of the temp image for drawing, by using frame=0. This is completely unneeded, because the variable frame is local to gen_menu_but(), which means that each time the function is used, frame is already equal to zero by default. Not only that, but using SetBuffer ImageBuffer(tempimage,frame) is also a waste, because the frame parameter of ImageBuffer() is optional, and 0 by default. You can ignore the line frame=0 entirely, and just use SetBuffer ImageBuffer(tempimage). Your code works, it just includes a lot of unnecessary steps that serve no purpose except to add more work for you to type and more clutter for you to read.

All your drawing goes fine for the first frame, then you change to the second frame with frame=1. This is also unnecessary; you could simply use SetBuffer ImageBuffer(tempimage,1) and save a line of code. Again, your drawing goes fine, and then we hit the big problem zone.

After you're done drawing to tempimage, you finally copy it into b\img, and then delete the temp with FreeImage tempimage. This is fine and good; then you lose it completely, because you exit the function without returning focus to the back buffer.

You grabbed the back buffer for drawing at the beginning of your program, which is fine, by using SetBuffer BackBuffer(). By the time you finish with gen_menu_but(), however, you have already changed that twice with SetBuffer ImageBuffer() calls. This means that the active buffer is no longer the back buffer, but still the temporary image you've created. Then, to make it worse, you free the image that the program is still trying to draw to. The result is that the buffer Blitz wants to draw to no longer exists, so to avoid crashing, it returns to the default: the FRONT buffer. Flip() becomes useless, the back buffer is ignored, and the program slows because it's once again trying to draw directly to the monitor itself.

Here's a rewrite of gen_menu_but() that solves all of these issues. The code itself is simpler and more compact, but I've added some spacing and comments so you can easily see what's going on.

Function gen_menu_but(txt$,action$,colour$)
	
	b.but = New but ; Make the button, same as before
	
	b\img=CreateImage(Len(txt$)*12,16,2) ; Make the button's image
	
	SetBuffer ImageBuffer(b\img) ; Go directly to the first frame
	
	; All the usual drawing stuff
	Color 0,0,0
	Rect 0,0,1024,1024,1
	Color 155,155,155
	Rect 0,0,ImageWidth(b\img)-1,ImageHeight(b\img)-1,0
	getcolour(colour$)
	Text ImageWidth(b\img)/2,ImageHeight(b\img)/2,txt$,1,1
	
	SetBuffer ImageBuffer(b\img,1) ; Get the second frame
	
	; Drawing stuff again
	Color 200,150,75
	Rect 0,0,1024,1024,1
	Color 155,155,155
	Rect 0,0,ImageWidth(b\img)-1,ImageHeight(b\img)-1,0
	getcolour(colour$)
	Text ImageWidth(b\img)/2,ImageHeight(b\img)/2,txt$,1,1
	MidHandle b\img
	
	b\click_cmd=action$
	
	; All done - return focus to the back buffer
	SetBuffer BackBuffer()
End Function
Hope this all helps! :D

Last edited 2011


Axel Wheeler(Posted 2011) [#7]
I think the frame=1 stuff was so that he could extend it into many more frames with a loop. But for the first two he just hard coded the commands twice.

But the fact that Blitz defaults to frontbuffer (if that's what you mean by monitor) when the buffer in SetBuffer() does not exist is interesting.