It is currently Wed Oct 21, 2020 2:35 am

All times are UTC + 1 hour




Post new topic Reply to topic  [ 4 posts ] 
Author Message
 Post subject: how can i improve this algorithm?
PostPosted: Mon May 04, 2020 7:42 pm 
Offline
Enthusiast
Enthusiast
User avatar

Joined: Fri Dec 04, 2015 9:26 pm
Posts: 290
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:

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


Top
 Profile  
Reply with quote  
 Post subject: Re: how can i improve this algorithm?
PostPosted: Sun Jun 07, 2020 4:24 am 
Offline
Addict
Addict
User avatar

Joined: Sun Mar 07, 2004 8:47 am
Posts: 1860
Location: Argentina
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:
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!)


Top
 Profile  
Reply with quote  
 Post subject: Re: how can i improve this algorithm?
PostPosted: Sun Jun 07, 2020 8:57 am 
Offline
Enthusiast
Enthusiast
User avatar

Joined: Sun Sep 11, 2016 2:17 pm
Posts: 731
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:
          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.


Top
 Profile  
Reply with quote  
 Post subject: Re: how can i improve this algorithm?
PostPosted: Sun Jun 07, 2020 5:38 pm 
Offline
Addict
Addict
User avatar

Joined: Sun Mar 07, 2004 8:47 am
Posts: 1860
Location: Argentina
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!)


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 4 posts ] 

All times are UTC + 1 hour


Who is online

Users browsing this forum: No registered users and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  

 


Powered by phpBB © 2008 phpBB Group
subSilver+ theme by Canver Software, sponsor Sanal Modifiye