Results 1 to 8 of 8

Thread: [RESOLVED] Drawing using BackGroundWorker, I don't see my where I'm wrong.

  1. #1

    Thread Starter
    I don't do your homework! opus's Avatar
    Join Date
    Jun 2000
    Location
    Good Old Europe
    Posts
    3,863

    Resolved [RESOLVED] Drawing using BackGroundWorker, I don't see my where I'm wrong.

    I'm using drawing routines that are all called by the .Paint Event of my Control.
    The calculation of the objects is done in the Routine "SystemTracks_Simulation", also the positional regions where the objects have been and are now get invalidated. This routine is called once for each Frame.

    If I use that routine in the GUI thread, everything works as it should. All the different objects are drawn.
    However if I use a BackGroundWorker-thread to call the "SystemTracks_Simulation" the respective objects are most of the time not drawn. Only if I do a ReScale (complete Redraw, i.e. Invalidate without Region) the objects are drawn. If the simulation in running the objects are only shown momentarily (which is the problem), if it isn't running the redraw will show all the objects.



    Here are the codelines that call the routine either by the BGW or directly.
    Code:
    If Not MainForm.BackgroundSystemTracksWorker.IsBusy Then
         MainForm.BackgroundSystemTracksWorker.RunWorkerAsync()
    Else
          debug_schreiben("SystemTracks_Sim ausgefallen! ")
    End If
    or
    Code:
    SystemTracks_Simulation()
    You're welcome to rate this post!
    If your problem is solved, please use the Mark thread as resolved button


    Wait, I'm too old to hurry!

  2. #2
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Posts
    12,397

    Re: Drawing using BackGroundWorker, I don't see my where I'm wrong.

    I think I know what may be happening as I had this happen a lot during some games that I made. In my case, one thing that would happen is that the drawing would occur before the physics causing terrible ripping and glitches. The solution in my case was a managed game loop. What does the actual drawing look like and are you using GDI+ or something else?
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | HtmlLessons | CssLessons | Code Tags | Sword of Fury - Jameram

  3. #3

    Thread Starter
    I don't do your homework! opus's Avatar
    Join Date
    Jun 2000
    Location
    Good Old Europe
    Posts
    3,863

    Re: Drawing using BackGroundWorker, I don't see my where I'm wrong.

    The (shortened) code that does draw one object (Track)
    Code:
    Private Sub TrackDrawing(ByRef e As Graphics, ByRef Pen As Pen, ByRef Brush As SolidBrush, ByVal Track As SystemTrack)
            'Draw a Systemtrack
            Dim j As Integer
            Dim SymbolPos As Point
            Dim Linksymbol As Bitmap
            Dim LinkString As String
            Try
               SymbolPos = Track.PixelPosition
               SymbolPos.X -= 16 'SymbolSize 32*32!!
                SymbolPos.Y -= 16
                Pen.Width = 2
                Pen.LineJoin = Drawing2D.LineJoin.Round
               ' .......
                Linksymbol = CType(Bitmap.FromFile(LinkDirectory & "/" & LinkString), Bitmap)
                Dim TheImage As New Bitmap(Linksymbol, 32, 32)
                TheImage.MakeTransparent()
                Dim ptsArray As Single()() = {New Single() {1, 0, 0, 0, 0}, New Single() {0, 1, 0, 0, 0}, New Single() {0, 0, 1, 0, 0}, New Single() {0, 0, 0, 0.5F, 0}, New Single() {0, 0, 0, 0, 1}}
                Dim clrMatrix As New System.Drawing.Imaging.ColorMatrix(ptsArray)
                Dim imgAttributes As New System.Drawing.Imaging.ImageAttributes
                imgAttributes.SetColorMatrix(clrMatrix, Imaging.ColorMatrixFlag.Default, Imaging.ColorAdjustType.Bitmap)
                e.DrawImage(TheImage, New Rectangle(SymbolPos.X, SymbolPos.Y, TheImage.Width, TheImage.Height), 0, 0, TheImage.Width, TheImage.Height, GraphicsUnit.Pixel, imgAttributes)
               Pen.Width = 1
                Dim Text As String
                If Track.TrackNumber = 0 Then
                    Text = "_" & Track.SystemID
                Else
                    Text = Track.TrackNumber.ToString
                End If
                Brush.Color = Pen.Color
                e.DrawString(Text, Me.Font, Brush, KursLinie(0).X + 11, KursLinie(0).Y + 11)
            Catch ex As Exception
                ErrLog_schreiben("TrackZeichnen", ex.Message)
            End Try
        End Sub
    My GameLoop looks like that
    Code:
     Public Sub TimeControll()
            Static LastSimTime As Integer = Environment.TickCount
            Dim ActualZeit As Integer
            Static CycleCounter As Double
            Try
                ActualZeit = Environment.TickCount
                If LastSimTime + SpeedFactor / FPS <= ActualZeit Then
                    Zeitschritte = 1
                    If ActualZeit - LastSimTime > SpeedFactor / FPS Then
                        ZeitSchritte = (ActualZeit - LastSimTime) / (SpeedFactor / FPS) 'If the time is lagging, do a bigger step
                    End If
                    LastSimTime = ActualZeit
                    Playtime = ((DateTime.UtcNow.Hour) * 60 + DateTime.UtcNow.Minute) * 60 + DateTime.UtcNow.Second
                   Simulation(MainForm, System.EventArgs.Empty)
                End If
                'Application.DoEvents()
                If StartStop = True Then
                    'as long as StartStop=True these Routine is calling itself again!
                    ExecuteAfterPause(CInt(SpeedFactor / FPS), New MethodInvoker(AddressOf TimeControl))
                End If
            Catch ex As Exception
                ErrLog_schreiben("Zeitkontrolle", ex.Message, StartStop.ToString, ActualZeit.ToString, Playtime.ToString, Mode.ToString)
            End Try
        End Sub
    The code from post #1 are called in "Simulation((MainForm, System.EventArgs.Empty)".
    I guess you do recognise your technique, and yes its the Pauser from JMcIlhinney
    You're welcome to rate this post!
    If your problem is solved, please use the Mark thread as resolved button


    Wait, I'm too old to hurry!

  4. #4
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    40,109

    Re: Drawing using BackGroundWorker, I don't see my where I'm wrong.

    I'd say that's the issue. You don't really want the Paint routine to be waiting on a background thread, but that means that your paint event may complete before your calculations. The results could be totally bizarre, depending on what you were doing. In the worst case, you could be sharing variables between the paint routine and the foreground, thus setting up a race condition for the painting.
    My usual boring signature: Nothing

  5. #5
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Posts
    12,397

    Re: Drawing using BackGroundWorker, I don't see my where I'm wrong.

    Edit - I already knew the information I asked for :/

    Altered post. It's certainly a race condition that's occurring like what Shaggy said. What you'll have to do is rework your code to try and prevent that as much as possible. One thing that I could see what would be causing it is your Try/Catch, that alone is such a resource hog. One other thing which is what I do on my game projects is set it up in this format:

    Step 1. Update
    Step 2. Draw
    Step 3. Render

    Each step has a separate thread and one will not perform until the other has finished. Finally my last suggestion is to change your game loop. While the one that I posted using JMcIlhinney's pauser works well, it doesn't work as efficiently as this one here:
    Code:
        Private game_thread As Threading.Thread
    
        Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
            quit = True
        End Sub
    
        Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
    
            game_thread = New Threading.Thread(AddressOf GameLoop)
            game_thread.Start()
    
        End Sub
    
        Private quit As Boolean = False
        Private Sub GameLoop()
    
            Dim s As New Stopwatch
            s.Start()
    
            'Update
            'Draw
            'Render
    
            'Loop
            While s.Elapsed <= TimeSpan.FromMilliseconds(16.6)
                Application.DoEvents()
            End While
    
            Console.WriteLine(s.ElapsedMilliseconds)
    
            s.Stop()
            If quit = False Then
                game_thread = New Threading.Thread(AddressOf GameLoop)
                game_thread.Start()
            End If
        End Sub
    The reason is because of the nature of the timer, it's just not a high precision instrument where as the stopwatch will be if the computer will allow it to be.
    Last edited by dday9; Apr 28th, 2014 at 02:09 PM.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | HtmlLessons | CssLessons | Code Tags | Sword of Fury - Jameram

  6. #6

    Thread Starter
    I don't do your homework! opus's Avatar
    Join Date
    Jun 2000
    Location
    Good Old Europe
    Posts
    3,863

    Re: Drawing using BackGroundWorker, I don't see my where I'm wrong.

    Quote Originally Posted by Shaggy Hiker View Post
    You don't really want the Paint routine to be waiting on a background thread, but that means that your paint event may complete before your calculations.
    I was thinking that the Paint routine would use the actually "known" values, i.e. possibly old data, but why doesn't it show up at all (except for a complete .Invalidate of the Control?).
    You're welcome to rate this post!
    If your problem is solved, please use the Mark thread as resolved button


    Wait, I'm too old to hurry!

  7. #7

    Thread Starter
    I don't do your homework! opus's Avatar
    Join Date
    Jun 2000
    Location
    Good Old Europe
    Posts
    3,863

    Re: Drawing using BackGroundWorker, I don't see my where I'm wrong.

    This one is really bugging me!
    I made a small example of what I'm trying to do, and guess what, it is working!
    Now I have to look real deep into my application where the failure is. Will report on results.

    The small example (Form1 has two Buttons (btnStartStop and btnAdd) and a PictureBox1 and a BGW

    Code:
    Public Class Form1
    
        Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint
            Try
                DrawObjects(e.Graphics, MyObjects, Color.Black)
                DrawObjects(e.Graphics, MyObjects2, Color.Red)
            Catch
                Debug.Print("error")
            End Try
        End Sub
    
        Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAdd.Click
            Dim p As New Point(0, 0)
            Dim o As New clEntity("LaLa", p)
            MyObjects.Add(o)
            p = New Point(PictureBox1.Width, 0)
            o = New clEntity("Bla", p)
            MyObjects2.Add(o)
        End Sub
    
        Private Sub btnStartStop_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnStartStop.Click
            Dim btn As Button = DirectCast(sender, Button)
            If btn.Text = "Start" Then
                btn.Text = "Stop"
                started = True
                GameLoop()
            Else
                btn.Text = "Start"
                started = False
            End If
        End Sub
    
        Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
            MyObjects = New List(Of clEntity)
            MyObjects2 = New List(Of clEntity)
            AddHandler PicBoxInvalidate, AddressOf PictureBox_Invalidate
        End Sub
    
        Private Sub PictureBox1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PictureBox1.Click
            Dim pic As PictureBox = DirectCast(sender, PictureBox)
            pic.Invalidate()
        End Sub
    
        Private Sub PictureBox_Invalidate(ByVal sender As Object, ByVal k As KarteEventArgs)
            PictureBox1.Invalidate(k.Region)
        End Sub
    
        Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
            MoveObjects(MyObjects, InvalRegions, 1, 1)
        End Sub
        
        Private Sub DrawObjects(g As Graphics, Objects As List(Of clEntity), Color As Color)
            If Not Objects Is Nothing Then
                For Each o As clEntity In Objects
                    Using Pen As New Pen(Color)
                        g.DrawLine(Pen, o.Position.X - 3, o.Position.Y, o.Position.X + 3, o.Position.Y)
                        g.DrawLine(Pen, o.Position.X, o.Position.Y - 3, o.Position.X, o.Position.Y + 3)
                    End Using
                Next
            End If
        End Sub
    End Class
    The module with all the logic. Note that MyObjects.Bewegung (Movement) is calculated using the BGW. MyObjects2.Bewegung is done on the GUI-thread. All the drawing is done in the Paint-Event
    Code:
    Module Module1
        Public MyObjects As List(Of clEntity)
        Public MyObjects2 As List(Of clEntity)
        Public Started As Boolean = False
        Public Event PicBoxInvalidate As EventHandler(Of KarteEventArgs)
    
        Public Class KarteEventArgs
            Inherits EventArgs
            Private _Region As Rectangle
            Public Property Region() As Rectangle
                Get
                    Return Me._Region
                End Get
                Set(ByVal value As Rectangle)
                    _Region = value
                End Set
            End Property
            Public Sub New(Optional ByVal Region As Rectangle = Nothing)
                Me._Region = Region
            End Sub
        End Class
    
        Public Sub GameLoop()
            Static LastTime As Integer = Environment.TickCount
            Dim ActualTime As Integer
            Dim FrameIntervall As Integer = 10
            ActualTime = Environment.TickCount
            If LastTime + FrameIntervall <= ActualTime Then
                
                If Not Form1.BackgroundWorker1.IsBusy Then
                    Form1.BackgroundWorker1.RunWorkerAsync()
                End If
            End If
            MoveObjects(MyObjects2, InvalRegions, 1, -1)
            LastTime = ActualTime
            If Started = True Then
                ExecuteAfterPause(FrameIntervall, New MethodInvoker(AddressOf GameLoop))
            End If
    
        End Sub
    
        Public Sub MoveObjects(ByVal Objects As List(Of clEntity), ByVal Regions As Queue(Of Region), ByVal Vertical As Integer, ByVal Horizontal As Integer)
            If Not Objects Is Nothing Then
                Dim RemoveObjects As New List(Of clEntity)
                For Each o As clEntity In Objects
                    Dim size As New Size(8, 8)
                    Dim p As New Point(o.Position.X - 4, o.Position.Y - 4)
                    Dim k As New KarteEventArgs
                    k.Region = New Rectangle(p, size)
                    RaiseEvent PicBoxInvalidate(Form1, k) 'to wipe off on the old position
                    
                    o.Bewegung(Vertical, Horizontal)
                    
                    p.X = o.Position.X - 3
                    p.Y = o.Position.Y - 3
                    k.Region = New Rectangle(p, size)
                    RaiseEvent PicBoxInvalidate(Form1, k) 'to draw the new position
                    'If outside of PictureBox, Remove it
                    If o.Position.X > Form1.PictureBox1.Width Or o.Position.X < 0 Or o.Position.Y > Form1.PictureBox1.Height Or o.Position.Y < 0 Then
                        RemoveObjects.Add(o)
                    End If
                Next
                If Not RemoveObjects.Count = 0 Then
                    For Each o As clEntity In RemoveObjects
                        Objects.Remove(o)
                    Next
                End If
            End If
        End Sub
       
    End Module
    The clEntity
    Code:
    Imports System.Math
    Public Class clEntity
        
        
    
    #Region "New Prozeduren"
    
        Public Sub New(ByVal _Name As String, ByVal _Position As Point)
            With Me
                .Name = _Name
                .Position = _Position
            End With
        End Sub
    
    #End Region
    
    #Region "Eigenschaften"
        Public Mutex As New System.Threading.Mutex
        Private _Name As String
        Public Property Name() As String
            Get
                Return _Name
            End Get
            Set(ByVal value As String)
                _Name = value
            End Set
        End Property
        Private _Position As Point
        Public Property Position() As Point
            Get
                Return _Position
            End Get
            Set(ByVal value As Point)
                _Position = value
            End Set
        End Property
       
    #End Region
    
    #Region "Methoden"
    
        Public Sub Bewegung(ByVal Vertical As Integer, ByVal Horizontal As Integer)
            Dim p As New Point(Position.X + Horizontal, Position.Y + Vertical)
            Position = p
        End Sub
    
    #End Region
    
    End Class
    You're welcome to rate this post!
    If your problem is solved, please use the Mark thread as resolved button


    Wait, I'm too old to hurry!

  8. #8

    Thread Starter
    I don't do your homework! opus's Avatar
    Join Date
    Jun 2000
    Location
    Good Old Europe
    Posts
    3,863

    Re: Drawing using BackGroundWorker, I don't see my where I'm wrong.

    Me.Stupid!
    I was precomputing the actual PixelLocation of each object when calculating the movements. That is because I'm using a chart projection that needs much calculation (Mercator). I try to compute each PixelPosition only once, and out of some reasons that does go along with the movement calculus.
    By moving that into another thread I missed that I need the correct (centre) position and size of the control. Guess what, I had those values in Public properties of the Form. No problem if you're on a single thread, however........................
    All those objects were drawn, however somewhere on a screen in Timbuktu or whatever.

    Thanks everybody for the helpfull hints.
    You're welcome to rate this post!
    If your problem is solved, please use the Mark thread as resolved button


    Wait, I'm too old to hurry!

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width