how can i improve this algorithm?

Advanced game related topics
User avatar
skinkairewalker
Enthusiast
Enthusiast
Posts: 633
Joined: Fri Dec 04, 2015 9:26 pm

how can i improve this algorithm?

Post by skinkairewalker »

The purpose of the code below is: To prevent the game from 'pausing' when the user clicks on the top menu of the window and also to capture the mouse position when clicking on a sprite in a fluid way (as in most games with a mouse click) .
can someone help me?

Code :

Code: Select all


Declare GameThread(*mem)

DeclareModule MOUSE
 
  ;MOUSE MODULE DRAFT
  ;BY MIJIKAI
  ;PB 5.62 x64 (Windows 10)

  Structure MOUSE_VECTOR_STRUCT
    X.f
    Y.f
  EndStructure
 
  Structure MOUSE_STRUCT
    Window.i
    WindowHandle.i
    Task.MSG
    Pos.MOUSE_VECTOR_STRUCT
    Factor.MOUSE_VECTOR_STRUCT
    Area.MOUSE_VECTOR_STRUCT
    Resize.b
    LeftButton.b
    LeftButtonDouble.b
    RightButton.b
    RightButtonDouble.b
  EndStructure
 
  Declare.i Init(Window.i)
  Declare.b Update()
  Declare.i Window()
  Declare.i WindowHandle()
  Declare.i Resize()
  Declare.f AreaX()
  Declare.f AreaY()
  Declare.f PosX()
  Declare.f PosY()
  Declare.f FactorX()
  Declare.f FactorY()
  Declare.b LeftButton()
  Declare.b RightButton()
  Declare.b LeftButtonDouble()
  Declare.b RightButtonDouble()
 
EndDeclareModule

Module MOUSE
 
  Global Mouse.MOUSE_STRUCT

  Procedure.w LoWord(Value.l)
    ProcedureReturn (Value & $FFFF)
  EndProcedure
 
  Procedure.w HiWord(Value.l)
    ProcedureReturn ((Value >> 16) & $FFFF)
  EndProcedure
 
  Procedure.i Init(Window.i)
    Protected Style.i
    If IsWindow(Window)
      ClearStructure(@Mouse,MOUSE_STRUCT)
      Mouse\Window = Window
      Mouse\WindowHandle = WindowID(Window)
      Mouse\Area\X = WindowWidth(Window)
      Mouse\Area\Y = WindowHeight(Window)
      Mouse\Factor\X = 1
      Mouse\Factor\Y = 1
      Style = GetClassLongPtr_(Mouse\WindowHandle,#GCL_STYLE)
      If Style
        SetClassLongPtr_(Mouse\WindowHandle,#GCL_STYLE,Style|#CS_DBLCLKS)
      EndIf
      ProcedureReturn @Mouse;returns pointer to MOUSE_STRUCT
    EndIf
  EndProcedure
 
  Procedure.b Update() 
    If PeekMessage_(@Mouse\Task,Mouse\WindowHandle,#Null,#Null,#PM_NOREMOVE)
      Mouse\LeftButtonDouble = #False
      Mouse\RightButtonDouble = #False
      If Mouse\Resize = #True
        Mouse\Factor\X = Mouse\Area\X / WindowWidth(Mouse\Window)
        Mouse\Factor\Y = Mouse\Area\Y / WindowHeight(Mouse\Window)
        Mouse\LeftButton = #False
        Mouse\RightButton = #False
      EndIf
      Select Mouse\Task\message
        Case #WM_MOUSEMOVE
          Mouse\Pos\X = LoWord(Mouse\Task\lParam) * Mouse\Factor\X
          Mouse\Pos\Y = HiWord(Mouse\Task\lParam) * Mouse\Factor\Y
          Mouse\Resize = #False
        Case #WM_LBUTTONDOWN
          Mouse\LeftButton = #True
        Case #WM_LBUTTONUP
          Mouse\LeftButton = #False
        Case #WM_LBUTTONDBLCLK
          Mouse\LeftButtonDouble = #True
        Case #WM_LBUTTONDOWN
          Mouse\RightButton = #True
        Case #WM_RBUTTONUP
          Mouse\RightButton = #False
        Case #WM_RBUTTONDBLCLK
          Mouse\RightButtonDouble = #True
        Case #WM_NCMOUSEMOVE
          Mouse\Resize = #True
      EndSelect
      ProcedureReturn #True
    EndIf
  EndProcedure
 
  Procedure.i Window()
    ProcedureReturn Mouse\Window
  EndProcedure
 
  Procedure.i WindowHandle()
    ProcedureReturn Mouse\WindowHandle
  EndProcedure
 
  Procedure.i Resize()
    PostMessage_(Mouse\WindowHandle,#WM_NCMOUSEMOVE,#Null,#Null)
  EndProcedure
 
  Procedure.f AreaX()
    ProcedureReturn Mouse\Area\X
  EndProcedure
 
  Procedure.f AreaY()
    ProcedureReturn Mouse\Area\Y
  EndProcedure
 
  Procedure.f PosX()
    ProcedureReturn Mouse\Pos\X
  EndProcedure
 
  Procedure.f PosY()
    ProcedureReturn Mouse\Pos\Y
  EndProcedure
 
  Procedure.f FactorX()
    ProcedureReturn Mouse\Factor\X
  EndProcedure
 
  Procedure.f FactorY()
    ProcedureReturn Mouse\Factor\Y
  EndProcedure
 
  Procedure.b LeftButton()
    ProcedureReturn Mouse\LeftButton
  EndProcedure
 
  Procedure.b RightButton()
    ProcedureReturn Mouse\RightButton
  EndProcedure
 
  Procedure.b LeftButtonDouble()
    Protected State.b = Mouse\LeftButtonDouble
    Mouse\LeftButtonDouble = #False
    ProcedureReturn State
  EndProcedure
 
  Procedure.b RightButtonDouble()
    Protected State.b = Mouse\RightButtonDouble
    Mouse\RightButtonDouble = #False
    ProcedureReturn State
  EndProcedure
 
EndModule

Procedure.i Render(Sprite.i);lazy example...
  Static OldX.f
  Static OldY.f
  Static Take = #True
  If MOUSE::RightButtonDouble()
    MessageBox_(#Null,"RightButtonDouble()","Test!",#Null)
  EndIf
 ; If MOUSE::LeftButtonDouble()
 ;   ResizeWindow(MOUSE::Window(),#PB_Ignore,#PB_Ignore,Random(800),Random(800))
 ;   MOUSE::Resize()
 ; EndIf
  If MOUSE::LeftButton()
    If MOUSE::PosX() > OldX And MOUSE::PosX() < (OldX + 32)
      If MOUSE::PosY() > OldY And MOUSE::PosY() < (OldY + 32)
        Take = #True
      EndIf
    EndIf
  Else
    Take = #False
  EndIf
  If Take = #True
    OldX = MOUSE::PosX() - 16
    OldY = MOUSE::PosY() - 16
  EndIf
  DisplayTransparentSprite(Sprite,OldX,OldY)
EndProcedure

Procedure.i Screen(Width.i,Height.i,Title.s)
  Protected Event.i
  Protected Style.i
  Protected Sprite.i
  Protected Exit.b
  
  
  Global direction = 1
  Global  playerX = 1
  Global  playerY = 1
  
  Style|#PB_Window_SystemMenu|#PB_Window_SizeGadget|#PB_Window_MaximizeGadget
  Style|#PB_Window_ScreenCentered|#PB_Window_SizeGadget|#PB_Window_MinimizeGadget
  If OpenWindow(0,0,0,Width,Height,Title,Style) & InitSprite()
    If OpenWindowedScreen(WindowID(0),0,0,Width,Height,#True,0,0)
      WindowBounds(0,320,320,#PB_Ignore,#PB_Ignore)
        CreateThread(@GameThread(),0)
        Repeat
            MOUSE::Update()
            Event = WindowEvent()
            Select Event
              Case #PB_Event_CloseWindow
                End
            EndSelect
          Until Event = #True

    EndIf
  EndIf
EndProcedure

Procedure GameThread(*mem)
  
  
  SetFrameRate(60)
  LoadSprite(1, #PB_Compiler_Home + "examples/sources/Data/PureBasicLogo.bmp")
  Sprite = CatchSprite(#PB_Any,?SPRITEDATA,#PB_Sprite_AlphaBlending)
  
  
  If Sprite And MOUSE::Init(0)

    Debug "working"
        Repeat
          ClearScreen($FFFFFF)
          Render(Sprite)
          
          ClipSprite(1, 0, 0, x, x/8)
      DisplaySprite(1, x, 100)
      DisplaySprite(1, x, x)
      DisplaySprite(1, 300-x, x)
      DisplaySprite(1, playerX, playerY)
    
      x + direction
      If x > 300 : direction = -1 : EndIf   ; moving back to the left with negative value
      If x < 0   : direction =  1 : EndIf   
          
          FlipBuffers()
        Until Exit = #True
  EndIf
  
EndProcedure  

UsePNGImageDecoder()

Screen(1024,768,#Null$)

DataSection
  SPRITEDATA:
  ;{ Size: 204 Bytes
  !dw 05089h, 0474Eh, 00A0Dh, 00A1Ah, 00000h, 00D00h, 04849h, 05244h, 00000h
  !dw 02000h, 00000h, 02000h, 00208h, 00000h, 0FC00h, 0ED18h, 000A3h, 00000h
  !dw 04993h, 04144h, 04854h, 0EDC7h, 0C156h, 0800Dh, 00830h, 00DA4h, 0D023h
  !dw 0DC05h, 0267Fh, 0C017h, 0FC21h, 0A219h, 0D0B5h, 02622h, 0F29Ah, 038E6h
  !dw 00A38h, 01134h, 04411h, 0021Ah, 0AF00h, 0505Bh, 0B2F4h, 08650h, 00B60h
  !dw 0C027h, 0AA81h, 06E2Dh, 0EC87h, 04468h, 03ECAh, 0B331h, 04492h, 0F529h
  !dw 0EE0Ah, 01290h, 02E10h, 0CA91h, 0CA77h, 0FD12h, 0CAC9h, 05788h, 06E78h
  !dw 05523h, 05DB9h, 01912h, 069ABh, 07DBBh, 0D07Fh, 0B7FEh, 0AA2Ah, 0C6CFh
  !dw 06833h, 056EFh, 0C9E0h, 02754h, 09FB0h, 0AE9Dh, 07BE9h, 0177Eh, 00979h
  !dw 0EF7Ah, 01CEBh, 091B4h, 0181Eh, 0AEAFh, 04FB1h, 007B3h, 0C093h, 0296Fh
  !dw 0FBFAh, 003BEh, 0AE4Bh, 0B92Ah, 0EC0Fh, 0B1D8h, 00000h, 00000h, 04549h
  !dw 0444Eh, 042AEh, 08260h, 0AE4Bh
  ;}
EndDataSection
dagcrack
Addict
Addict
Posts: 1868
Joined: Sun Mar 07, 2004 8:47 am
Location: Argentina
Contact:

Re: how can i improve this algorithm?

Post by dagcrack »

Hi, it seems there are multiple flaws within the code. Of course this has to be compiled with "Enable threadsafe" on, otherwise it wouldn't work. That could be the reason why the program hangs in your system. Here it would crash almost instantly without user interaction if this wasn't compiled as a thread-safe executable.

Here's a quick and dirty way for mouse picking of 2D objects, namely rectangles:
I kept the mouse library you were using but there are issues (read after the code)

Code: Select all

EnableExplicit

Declare GameThread(*mem)

DeclareModule MOUSE
	
	;MOUSE MODULE DRAFT
	;BY MIJIKAI
	;PB 5.62 x64 (Windows 10)
	
	Structure MOUSE_VECTOR_STRUCT
		X.f
		Y.f
	EndStructure
	
	Structure MOUSE_STRUCT
		Window.i
		WindowHandle.i
		Task.MSG
		Pos.MOUSE_VECTOR_STRUCT
		Factor.MOUSE_VECTOR_STRUCT
		Area.MOUSE_VECTOR_STRUCT
		Resize.b
		LeftButton.b
		LeftButtonDouble.b
		RightButton.b
		RightButtonDouble.b
	EndStructure
	
	Declare.i Init(Window.i)
	Declare.b Update()
	Declare.i Window()
	Declare.i WindowHandle()
	Declare.i Resize()
	Declare.f AreaX()
	Declare.f AreaY()
	Declare.f PosX()
	Declare.f PosY()
	Declare.f FactorX()
	Declare.f FactorY()
	Declare.b LeftButton()
	Declare.b RightButton()
	Declare.b LeftButtonDouble()
	Declare.b RightButtonDouble()
	
EndDeclareModule

Module MOUSE
	
	Global Mouse.MOUSE_STRUCT
	
	Procedure.w LoWord(Value.l)
		ProcedureReturn (Value & $FFFF)
	EndProcedure
	
	Procedure.w HiWord(Value.l)
		ProcedureReturn ((Value >> 16) & $FFFF)
	EndProcedure
	
	Procedure.i Init(Window.i)
		Protected Style.i
		If IsWindow(Window)
			ClearStructure(@Mouse,MOUSE_STRUCT)
			Mouse\Window = Window
			Mouse\WindowHandle = WindowID(Window)
			Mouse\Area\X = WindowWidth(Window)
			Mouse\Area\Y = WindowHeight(Window)
			Mouse\Factor\X = 1
			Mouse\Factor\Y = 1
			Style = GetClassLongPtr_(Mouse\WindowHandle,#GCL_STYLE)
			If Style
				SetClassLongPtr_(Mouse\WindowHandle,#GCL_STYLE,Style|#CS_DBLCLKS)
			EndIf
			ProcedureReturn @Mouse;returns pointer to MOUSE_STRUCT
		EndIf
	EndProcedure
	
	Procedure.b Update()
		If PeekMessage_(@Mouse\Task,Mouse\WindowHandle,#Null,#Null,#PM_NOREMOVE)
			Mouse\LeftButtonDouble = #False
			Mouse\RightButtonDouble = #False
			If Mouse\Resize = #True
				Mouse\Factor\X = Mouse\Area\X / WindowWidth(Mouse\Window)
				Mouse\Factor\Y = Mouse\Area\Y / WindowHeight(Mouse\Window)
				Mouse\LeftButton = #False
				Mouse\RightButton = #False
			EndIf
			Select Mouse\Task\message
				Case #WM_MOUSEMOVE
					Mouse\Pos\X = LoWord(Mouse\Task\lParam) * Mouse\Factor\X
					Mouse\Pos\Y = HiWord(Mouse\Task\lParam) * Mouse\Factor\Y
					Mouse\Resize = #False
				Case #WM_LBUTTONDOWN
					Mouse\LeftButton = #True
				Case #WM_LBUTTONUP
					Mouse\LeftButton = #False
				Case #WM_LBUTTONDBLCLK
					Mouse\LeftButtonDouble = #True
				Case #WM_LBUTTONDOWN
					Mouse\RightButton = #True
				Case #WM_RBUTTONUP
					Mouse\RightButton = #False
				Case #WM_RBUTTONDBLCLK
					Mouse\RightButtonDouble = #True
				Case #WM_NCMOUSEMOVE
					Mouse\Resize = #True
			EndSelect
			ProcedureReturn #True
		EndIf
	EndProcedure
	
	Procedure.i Window()
		ProcedureReturn Mouse\Window
	EndProcedure
	
	Procedure.i WindowHandle()
		ProcedureReturn Mouse\WindowHandle
	EndProcedure
	
	Procedure.i Resize()
		PostMessage_(Mouse\WindowHandle,#WM_NCMOUSEMOVE,#Null,#Null)
	EndProcedure
	
	Procedure.f AreaX()
		ProcedureReturn Mouse\Area\X
	EndProcedure
	
	Procedure.f AreaY()
		ProcedureReturn Mouse\Area\Y
	EndProcedure
	
	Procedure.f PosX()
		ProcedureReturn Mouse\Pos\X
	EndProcedure
	
	Procedure.f PosY()
		ProcedureReturn Mouse\Pos\Y
	EndProcedure
	
	Procedure.f FactorX()
		ProcedureReturn Mouse\Factor\X
	EndProcedure
	
	Procedure.f FactorY()
		ProcedureReturn Mouse\Factor\Y
	EndProcedure
	
	Procedure.b LeftButton()
		ProcedureReturn Mouse\LeftButton
	EndProcedure
	
	Procedure.b RightButton()
		ProcedureReturn Mouse\RightButton
	EndProcedure
	
	Procedure.b LeftButtonDouble()
		Protected State.b = Mouse\LeftButtonDouble
		Mouse\LeftButtonDouble = #False
		ProcedureReturn State
	EndProcedure
	
	Procedure.b RightButtonDouble()
		Protected State.b = Mouse\RightButtonDouble
		Mouse\RightButtonDouble = #False
		ProcedureReturn State
	EndProcedure
	
EndModule


;##########################

Structure ITEM
	sprite.i
	x.i
	y.i
	width.i
	height.i
EndStructure

InitSprite()

Define.i hWnd = OpenWindow( #PB_Any, 0, 0, 800, 600, "Sprite mouse picking", #PB_Window_SystemMenu | #PB_Window_ScreenCentered )

If IsWindow(hWnd)
	If OpenWindowedScreen( WindowID(hWnd), 0, 0, 800, 600, 0, 0, 0 )
		
		Define.i Event, Quit, Sprite, i
		Define.i offset_x, offset_y
		
		Define.ITEM *picked = #Null
		NewList Item.ITEM()
		
		sprite = LoadSprite( #PB_Any, #PB_Compiler_Home + "examples/sources/Data/PureBasicLogo.bmp" )
		If IsSprite(sprite)
			ClipSprite( sprite, 230, 0, 64, 64 )
		Else
			Debug "Error loading sprite!"
			CallDebugger
			End
		EndIf
		
		; populate some items.
		For i=0 To 9
			If AddElement(Item())
				Item()\sprite = sprite
				Item()\x = Random(ScreenWidth())
				Item()\y = Random(ScreenHeight())
				Item()\width = SpriteWidth(Item()\sprite)
				Item()\height = SpriteHeight(Item()\sprite)
			EndIf
		Next
		
		MOUSE::Init(hWnd)
		Repeat
			
			;input / logic
			
			If MOUSE::Update()
				If MOUSE::LeftButton()
					
					If Not *picked
						ForEach Item()
							
							If MOUSE::PosX() => Item()\x And MOUSE::PosX() <= Item()\x + Item()\width
								If MOUSE::PosY() => Item()\y  And MOUSE::PosY() <= Item()\y + Item()\height
									*picked = Item()
									offset_x = Item()\x - MOUSE::PosX() ; you need to store the local offset between the mouse and the sprite.
									offset_y = Item()\y - MOUSE::PosY()
									MoveElement( Item(), #PB_List_Last ) ; in case you want to bring the item forward.
									Break
								EndIf
							EndIf
							
						Next
						
					Else
						*picked\x = MOUSE::PosX()+offset_x
						*picked\y = MOUSE::PosY()+offset_y
					EndIf
					
				Else
					*picked = #Null
				EndIf
				
			EndIf
			
			Repeat
				Event = WindowEvent()
				Select Event
					Case #PB_Event_CloseWindow
						Quit = #True
				EndSelect
			Until Not Event
			
			;rendering / screen
			ClearScreen( RGB(128, 128, 128) )
			
			ForEach Item()
				DisplaySprite( Item()\sprite, Item()\x, Item()\y )
			Next
			
			FlipBuffers()
			Delay(0)
			
		Until Quit
	EndIf
EndIf

The way the module is handling the mouse events could lead to lack of release or other issues since it isn't working on a callback level. Which is to say this isn't the "strongest" way to deal with the mouse in Windows but it is a good draft.

Given more items and specially those off-screen you would have to perform some type of culling or filtering to introduce a leaner list for search. For most small games and programs this way "just works" and any further optimization could be seen as a waste of time.

Try not to fall into premature optimization rabbit holes, an extra thread for this is probably not required. Multi-threading will introduce serious issues if you are not used to sharing resources within multiple threads and know the limitations of the graphics library for instance. You could encounter multiple walls and other unforeseen consequences for no good reason.

Additional threads should be used, at least in game dev to unload the CPU of certain tasks such as general IO (for example loading of textures onto CPU memory so then the primary thread could upload it to the GPU memory because the context is not shared within threads typically and the later operation is often quick). Another good purpose for an additional thread is path finding and other AI related tasks where realtime may not be required and certain delays could be implemented to save cycles.

The moment you start sharing resources within threads you'll realize the need for a mutex, a semaphore or other ways to sync your loops.

In other words unless you need it and there's no other way try to avoid unnecessary threads. Once you detect a bottleneck and you think an additional thread would help, then go ahead. Everything has it's place.

Things to improve:
1) make a "rect inside point" function so you can later on use this check in other parts of the game.
2) move everything onto structures to keep the code clean.
3) add error checking for everything and always have a contingency plan.

Cheers!
! Black holes are where God divided by zero !
My little blog!
(Not for the faint hearted!)
User avatar
Mijikai
Addict
Addict
Posts: 1360
Joined: Sun Sep 11, 2016 2:17 pm

Re: how can i improve this algorithm?

Post by Mijikai »

The mouse update function just needs to be called before every WindoEvent() and it wont miss messages!
There will be no difference to a Callback then.

Just like this:

Code: Select all

          Repeat
            MOUSE::Update()  
            Event = WindowEvent()
            Select Event
               Case #PB_Event_CloseWindow
                  Quit = #True
            EndSelect
         Until Not Event
Also dont forget to use SetFrameRate() to make the game work the same across different computers.
Otherwise custom timing code is needed.

I would also suggest to use VSync.
dagcrack
Addict
Addict
Posts: 1868
Joined: Sun Mar 07, 2004 8:47 am
Location: Argentina
Contact:

Re: how can i improve this algorithm?

Post by dagcrack »

Oh I see!

However I try to avoid code that could break by swapping its calling order that's why I use a callback and keep all the messages constrained within but I know its a matter of preference. The draft is a very good starting point!

While we're at it and depending on the game he's working on he could start using a vector library to clean things up and get rid of independent "x, y" variables everywhere.

Another coding exercise I thought of could be a release behavior in which if the action is cancelled (could be the tile was misplaced or the action was user cancelled) the tile/sprite would return smoothly to the starting point using an easing function.

I recall seeing an easing module around the forum based on well known functions. (I'm just suggesting this because he mentioned the word "fluid" and easing always comes to mind).

Now I'm the one jumping onto a rabbit hole aren't I? :D
! Black holes are where God divided by zero !
My little blog!
(Not for the faint hearted!)
Post Reply