0

This is really starting to frustrate me.

Basically, this is part of a script I am coding for GTA IV (when it's done I might publish the mod on my website) and I need to exit this loop when the user presses a defined key in-game. Of course, I can't use KeyPreview because this is a class library (GTA IV plugins have to use .net.dll).

Here is what I have tried so far (obviously not the full code), as suggested on numerous websites:

Loop:

Sub CarFunctions()
    Dim veh = Player.Character.CurrentVehicle
    Do
        Application.DoEvents()
        If CancelLoop = True Then
            Exit Do
        End If
        Native.Function.Call("SET_VEH_INDICATORLIGHTS", veh, True)
        Native.Function.Call("FORCE_CAR_LIGHTS", veh, 1)
        Wait(500)
        Native.Function.Call("SET_VEH_INDICATORLIGHTS", veh, False)
        Native.Function.Call("FORCE_CAR_LIGHTS", veh, 2)
        Wait(500)
    Loop
End Sub

KeyDown Event:

Private Sub VehicleControls_KeyDown(sender As Object, e As GTA.KeyEventArgs) Handles Me.KeyDown
    Try
        Dim veh = Player.Character.CurrentVehicle
        If e.Key = Keys.NumPad5 Then
            CancelLoop = True
        End If
    Catch ex As Exception
        Game.Console.Print(ex.ToString)
    End Try
End Sub

The problem, I know, is that the program does not detect any keypress events while it is in a loop. I tried to use multithreading, but it throws an exception in the game and I cannot use an invoke method.

Thank you in advance.

Dog Lover
  • 618
  • 1
  • 6
  • 18
  • Sorry about having misjudged you. Let me rephrase my advice: you might be a knowledgeable programmer in other formats (even similar enough to VB.NET, like VB or VBScript), but your code (and your question) indicates that your .NET knowledge is not too solid. Bear in mind that VB.NET allows to use big proportions of VB6 code and this is what your code transmits: not purely-speaking VB.NET (but old code which compiles). Sorry if my tone was too aggressive; I wasn't trying to offend, just to be clear (being very clear lately in order to avoid past misunderstandings). – varocarbas Aug 20 '15 at 11:59
  • @varocarbas Thank you - it's okay. However, I am actually a .NET programmer (I made a POS application in VB; now working on a video library), but your advice and opinion certainly is valid. I'm always open to more effective/preferable methods of coding. – Dog Lover Aug 20 '15 at 12:05
  • Is CancelLoop a public boolean? – Alessandro Mandelli Aug 20 '15 at 13:20
  • @AlessandroMandelli Yes – Dog Lover Aug 20 '15 at 13:29
  • Low level keyboard hook? http://www.pinvoke.net/default.aspx/user32.setwindowshookex – Alessandro Mandelli Aug 20 '15 at 13:38

3 Answers3

0

In general 'exit control structure' key words, such as exit do, should be avoided.

https://msdn.microsoft.com/en-us/library/eked04a7.aspx

Dim index As Integer = 0
Do
    Debug.Write(index.ToString & " ")
    index += 1
Loop Until index > 10

I think the loop until is what you are looking for. You may have to use a while loop instead of a do while to support your specific logic. This is my attempt.

Sub CarFunctionsTask()
  Task.Factory.StartNew(Sub()
    Dim veh = Player.Character.CurrentVehicle
    Do
        If CancelLoop = False Then
            Native.Function.Call("SET_VEH_INDICATORLIGHTS", veh, True)
            Native.Function.Call("FORCE_CAR_LIGHTS", veh, 1)
            Wait(500)
            Native.Function.Call("SET_VEH_INDICATORLIGHTS", veh, False)
            Native.Function.Call("FORCE_CAR_LIGHTS", veh, 2)
            Wait(500)
        End If

    Loop Until CancelLoop
  End Sub)
End Sub
codemonkeyliketab
  • 320
  • 1
  • 2
  • 17
  • Unfortunately I tried the `Loop Until` but it also did not work. The problem, I know, is that the program does not detect any keypress events while it is in a loop. I tried to use multithreading, but it throws an exception in the game and I cannot use an invoke method. – Dog Lover Aug 20 '15 at 13:03
  • I understand your problem now. The thread is never free to do other stuff when the while loop is going. You need to put your logic in another thread like my example above so the thread that processes events can be free to do so. – codemonkeyliketab Aug 20 '15 at 13:17
  • @DogLover If the game is throwing an exception, do you happen to know if `Native.Function.Call` needs to be called back on the UI thread? – Mike Guthrie Aug 20 '15 at 18:54
  • @MikeGuthrie I'd suspect so. Unfortunately there's a lack of documentation. – Dog Lover Aug 20 '15 at 21:18
  • @DogLover I noticed you were getting an exception using MikeGuthrie's answer, which should not be happening unless you are using .NET framework 4.0, which has a known bug with tasks and how they marshal code back to the UI thread. I have low reputation so I can't post on his answer so I posted here. – codemonkeyliketab Aug 21 '15 at 13:08
  • @codemonkeyliketab Yes, I am using the .NET Framework 4.0. – Dog Lover Aug 22 '15 at 00:28
0

I think codemonkeyliketab is close to the answer, in that the loop needs to run on a background thread to not block keypress events. I would recommend something similar to what he's done, but using the calling context to perform the Native.Function.Call lines, as so:

Sub CarFunctions()
    Dim veh = Player.Character.CurrentVehicle
    Dim callingContext = System.Threading.Tasks.TaskScheduler.FromCurrentSynchronizationContext

    System.Threading.Tasks.Task.Factory.StartNew(
        Sub()
            While CancelLoop = False
                Dim t As Task
                t = System.Threading.Tasks.Task.Factory.StartNew(
                    Sub()
                        Native.Function.Call("SET_VEH_INDICATORLIGHTS", veh, True)
                        Native.Function.Call("FORCE_CAR_LIGHTS", veh, 1)
                    End Sub,
                    System.Threading.CancellationToken.None,
                    System.Threading.Tasks.TaskCreationOptions.None,
                    callingContext)
                t.Wait()
                Wait(500)
                t = System.Threading.Tasks.Task.Factory.StartNew(
                    Sub()
                        Native.Function.Call("SET_VEH_INDICATORLIGHTS", veh, False)
                        Native.Function.Call("FORCE_CAR_LIGHTS", veh, 2)
                    End Sub,
                    System.Threading.CancellationToken.None,
                    System.Threading.Tasks.TaskCreationOptions.None,
                    callingContext)
                t.Wait()
                Wait(500)
            End While
        End Sub)
End Sub
Mike Guthrie
  • 4,029
  • 2
  • 25
  • 48
  • Thanks Mike, I'll see if it works when I get home. Would it work with `Dim t as New System.Threading.Thread(AddressOf X) t.Start()`, or does it have to use a Task Factory? – Dog Lover Aug 21 '15 at 03:01
  • @DogLover Not sure. I used Task Factory so that I could pass in the TaskScheduler instance which should execute the Call functions back on the UI thread, in an attempt to fix the exception thrown when running your code as-is on a background thread. – Mike Guthrie Aug 21 '15 at 03:29
  • I tried your code but once again the plugin threw an exception. I persevered and encountered a solution which did work which I have posted as an answer. Thank you for your time; I really appreciate it. – Dog Lover Aug 21 '15 at 11:20
0

With the help of Mike Guthrie and codemonkeyliketab I managed to solve the problem (not quite perfectly, however) by using isKeyPressed() inside the loop:

Do
    If Me.isKeyPressed(Keys.Decimal) Then
        Exit Do
    End If
    Native.Function.Call("SET_VEH_INDICATORLIGHTS", veh, True)
    Native.Function.Call("FORCE_CAR_LIGHTS", veh, 1)
    Wait(500)
    Native.Function.Call("SET_VEH_INDICATORLIGHTS", veh, False)
    Native.Function.Call("FORCE_CAR_LIGHTS", veh, 2)
    Wait(500)
Loop

Thank you for all your answers.

Dog Lover
  • 618
  • 1
  • 6
  • 18
  • I thought that you have understood what I explained: from inside the loop you cannot access anything outside it. By assuming that Me.isKeyPressed is a real thing (what does not seem the case according to google.com), it would be an event-like situation which cannot be called inside the loop (events are not like functions you can call directly; they are only triggered as a response to the target behaviour). If your code is inside the main/GUI thread, no event (= autonomous behaviour precisely run on this thread) would be triggered. – varocarbas Aug 22 '15 at 09:10
  • You might set the loop inside a BackgroundWorker and create a global variable which might be updated via events (volatile in C#, although not sure in VB.NET: http://stackoverflow.com/questions/1095623/volatile-equivalent-in-vb-net). In any case, you might find a different approach by carefully thinking about the algorithm; and logically properly understanding how events/threads work on .NET. – varocarbas Aug 22 '15 at 09:13
  • Could you please explain how can you make Me.IsKeyPressed work? This is the only MSDN reference of IsKeyPressed: https://msdn.microsoft.com/en-us/library/cc136802(v=vs.85).aspx (what clearly cannot be called as you do it). So... from the perspective of any reader this is a wrong code. If this is a specific method you created, you should include the definition in your code (additionally, I guess that it includes some multithreading what is also not being referred here). I meant that nobody can take your code and compile it; it is not even helpful to understand what should be done. – varocarbas Aug 24 '15 at 13:11
  • @varocarbas I think you're failing to take into account that this is not pure VB.NET code. A lot of it comes from the GTA IV ScriptHook. – Dog Lover Aug 24 '15 at 21:34
  • All the answers you have got are fine with any VB.NET version; yours not. Also and as explained, what you aim cannot be accomplished directly: events cannot be triggered inside a loop which is being executed in the main thread. Anyway... I have spent enough time here; I have tried to help and shared my impressions on various fronts. More than enough, I think. – varocarbas Aug 24 '15 at 22:10