0

I have the following code in which I have an infinite loop that updates the state of a variable running in a thread, and in another thread, I have an infinite loop that prints the state of the said variable each second, the problem is that although the state updates correctly in the first thread, the state remains unaffected in the second one.

What could I do to solve this? I thought that using threads would solve the issue, also I'm not that well versed in asynchronous programing

inside main.py

import cv2 as cv
from utils import WindowCapture
import numpy as np
import os
import time
from threading import Thread
from threading import Event
import pyautogui
from queue import Queue


os.chdir(os.path.dirname(os.path.abspath(__file__)))

wincap = WindowCapture("DragonBallOnline")
global toggle
global enemyState

# >>> pyautogui.mouseDown(); pyautogui.mouseUp()  # does the same thing as a left-button mouse click
# >>> pyautogui.mouseDown(button='right')  # press the right button down
# >>> pyautogui.mouseUp(button='right', x=100, y=200)  # move the mouse to 100, 200, then release the right button up.


def targetIsFullHealth(enemy):
    return (enemy == np.array([65, 27, 234])).all()


def targetIsDead(enemy):
    return (enemy == np.array([26, 10, 95])).all()


def targetIsUndefined(enemy):
    return (enemy != np.array([65, 27, 234])).all() and (enemy != np.array([97, 155, 146])).all()


def combatLoop():  # unused
    ## target enemy
    ## if target is full health attack
    ## if target is dead  wait and loot
    ## if target is undefined search new target
    pass


def bot():  # unused
    # Debug function not used currently
    while True:
        ##do stuff
        EnemyLife = wincap.get_screenshot(902, 70, 120, 16)
        Items = wincap.get_screenshot(621, 163, 662, 749)
        Radar = wincap.get_screenshot(1750, 53, 157, 157)
        cv.imshow("EnemyLife", EnemyLife)
        cv.imshow("Items", Items)
        cv.imshow("Radar", Radar)
        # print(EnemyLife[10][5])

        if (EnemyLife[10][5] == np.array([65, 27, 234])).all():
            print("target is full health")
            rotateCursor()
        elif (EnemyLife[10][5] == np.array([97, 155, 146])).all():
            print("Target is dead")
        else:
            print("No target")

        if cv.waitKey(1) == ord("-"):
            cv.destroyAllWindows()
            break

    print("Done!")


def rotateCursor():  # unused
    # pyautogui.mouseDown(button="right")
    # pyautogui.mouseUp(button="right", x=10, y=1)
    pass


def updateEnemyState(threadname):
    global toggle
    global enemyState
    toggle = True

    while True:
        EnemyLife = wincap.get_screenshot(902, 70, 120, 16)
        cv.imshow("EnemyLife", EnemyLife)
        enemy = EnemyLife[10][5]

        if targetIsDead(enemy):
            enemyState = "dead"

        elif targetIsFullHealth(enemy):
            enemyState = "alive"

        else:
            enemyState = "undefined"

        Event().wait(1.0 / 60)
        print("Thread1 :", enemyState)

        if cv.waitKey(1) == ord("-"):
            cv.destroyAllWindows()
            toggle = False
            break


def macros(threadname):

    while toggle:
        Event().wait(1.0)

        print("Thread2 :", enemyState)


if __name__ == "__main__":
    print("test")
    # WindowCapture.list_window_names();

    thread1 = Thread(target=updateEnemyState, args=("Thread-1",))
    thread2 = Thread(target=macros, args=("Thread-1",))
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()


inside utils.py

import numpy as np
import win32gui, win32ui, win32con


class WindowCapture:

    # properties
    w = 0
    h = 0
    hwnd = None
    cropped_x = 0
    cropped_y = 0
    offset_x = 0
    offset_y = 0

    # constructor
    def __init__(self, window_name=None):
        # find the handle for the window we want to capture
        if window_name is None:
            self.hwnd = win32gui.GetDesktopWindow()
        else:
            self.hwnd = win32gui.FindWindow(None, window_name)
            if not self.hwnd:
                raise Exception('Window not found: {}'.format(window_name))

        # get the window size
        window_rect = win32gui.GetWindowRect(self.hwnd)
        self.w = window_rect[2] - window_rect[0]
        self.h = window_rect[3] - window_rect[1]

        # account for the window border and titlebar and cut them off
        #border_pixels = 8
        #titlebar_pixels = 30
        border_pixels = 0
        titlebar_pixels = 0
        self.w = self.w - (border_pixels * 2)
        self.h = self.h - titlebar_pixels - border_pixels
        self.cropped_x = border_pixels
        self.cropped_y = titlebar_pixels

        # set the cropped coordinates offset so we can translate screenshot
        # images into actual screen positions
        self.offset_x = window_rect[0] + self.cropped_x
        self.offset_y = window_rect[1] + self.cropped_y

    def get_screenshot(self,startX=None,startY=None,width=None,height=None):
        if startX is None : startX = self.cropped_x
        if startY is None : startY = self.cropped_y
        if width is None : width = self.w
        if height is None : height = self.h


        # get the window image data
        wDC = win32gui.GetWindowDC(self.hwnd)
        dcObj = win32ui.CreateDCFromHandle(wDC)
        cDC = dcObj.CreateCompatibleDC()
        dataBitMap = win32ui.CreateBitmap()
        dataBitMap.CreateCompatibleBitmap(dcObj, width, height)
        cDC.SelectObject(dataBitMap)
        cDC.BitBlt((0, 0), (width, height), dcObj, (startX, startY), win32con.SRCCOPY)

        # convert the raw data into a format opencv can read
        #dataBitMap.SaveBitmapFile(cDC, 'debug.bmp')
        signedIntsArray = dataBitMap.GetBitmapBits(True)
        img = np.fromstring(signedIntsArray, dtype='uint8')
        img.shape = (height, width, 4)

        # free resources
        dcObj.DeleteDC()
        cDC.DeleteDC()
        win32gui.ReleaseDC(self.hwnd, wDC)
        win32gui.DeleteObject(dataBitMap.GetHandle())

        img = img[...,:3]
        img = np.ascontiguousarray(img)

        return img

    @staticmethod
    def list_window_names():
        def winEnumHandler(hwnd, ctx):
            if win32gui.IsWindowVisible(hwnd):
                print(hex(hwnd), win32gui.GetWindowText(hwnd))
        win32gui.EnumWindows(winEnumHandler, None)

    # translate a pixel position on a screenshot image to a pixel position on the screen.
    # pos = (x, y)
    # WARNING: if you move the window being captured after execution is started, this will
    # return incorrect coordinates because the window position is only calculated in
    # the __init__ constructor.
    
    def get_screen_position(self, pos):
        return (pos[0] + self.offset_x, pos[1] + self.offset_y)

I have provided all the code one needs to recreate an example, although the most important parts are hardcoded hence it is a pixel detector, you might need to use another window name and another pixel position and color values

The initial and first state change:

Thread1 : alive
Thread1 : alive
Thread1 : alive
Thread1 : alive
Thread1 : alive
Thread2 : alive
Thread1 : alive
Thread1 : alive
Thread1 : alive
Thread1 : alive
Thread1 : alive

Debug info

Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread2 : alive
Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread1 : dead

Thread2's enemyState should have been dead My guess is that enemyState remains locked inside thread2 for some reason.

4
  • 1
    So you have 3 functions that you are calling that IDK how they work. I adapted your code to use random numbers to test it and everything works fine for me. I would debug those functions you call in your if statements from the update thread. Commented Jun 4, 2021 at 16:46
  • 1
    Please edit your question to provide a minimal reproducible example. There are several missing parts (likely from missing imports) and several parts – namely screen filling and capturing – that seem unrelated to the stated problem but will seriously complicate analysing the problem. Commented Jun 4, 2021 at 17:20
  • I have provided all the requested and relevant information, I hope it helps. Commented Jun 4, 2021 at 17:37
  • 2
    In this case, the problem is your timr.sleep(1) in thread2. EVERY TIME you see "Thread1: alive", you have pushed another item onto your queue. but thread2 can only pull them out once a second. By the time thread2 pulls one, you have 12 copies of "alive" in the queue before it gets to "dead". You should (a) only push a queue item when the state changes, and (b) forget the time.sleep(1), since queue.get() will block. Commented Jun 4, 2021 at 20:45

2 Answers 2

3

The global statement has to be in each function that changes the value of toogle. You can't just include it once. Move that statement into updateEnemyState and it should start working. The macros function doesn't need that, because it doesn't change the value. And you might fix the spelling; it should be toggle.

Sign up to request clarification or add additional context in comments.

1 Comment

I have remedied those issues, however, they did not remove the problem, I will post additional debug info as soon as possible.
0

After some trial and error, I have found out that time.sleep() is blocking the thread, used Event().wait(1.0) from Threading also instead of queue implementation I have decided that global variables would be best for simplicity's sake.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.