bombsimon / hltv-python

📺 HLTV live score implementation with Socket.IO
MIT License
15 stars 3 forks source link

is there any possible way to register async function? #7

Closed Soulbadguy54 closed 2 years ago

Soulbadguy54 commented 3 years ago

Example contains lines:

@classmethod
def on_connect(cls):
    """
    A simple callback to ensure we got connected.
    """
    print("connected!")

and:

live_score.on(live_score.EVENT_CONNECT, kill_feed.on_connect) What if the "on_connect" etc functions should be async? For write something in DB as example.

And additional. What should i do to get info from 2+ matches in the same time? I tried to make several instanses of Livescore in cycle, but the first await socket.wait() blocks everything. So, during iteration i can get data only from the first match (1st iter)

bombsimon commented 3 years ago

Good call, it makes way more sense to use async callbacks since we use the AsyncClient now! I changed the API and since I haven't published this to PyPy or even created any release this is now on master.

The wait() is optional and only needed if you don't have any other task going for you. The example now has an infinite loop that doesn't exit after 5 iterations so we no longer need wait(). By doing this you should be able to get a socket for two different games and add your events to both without blocking on one of the sockets.

Let me know if this helps! If not, maybe better to open a new issue to track multiple streams since this feels more related to the async callbacks.

Soulbadguy54 commented 3 years ago

Good call, it makes way more sense to use async callbacks since we use the AsyncClient now! I changed the API and since I haven't published this to PyPy or even created any release this is now on master.

The wait() is optional and only needed if you don't have any other task going for you. The example now has an infinite loop that doesn't exit after 5 iterations so we no longer need wait(). By doing this you should be able to get a socket for two different games and add your events to both without blocking on one of the sockets.

Let me know if this helps! If not, maybe better to open a new issue to track multiple streams since this feels more related to the async callbacks.

Hello, thx for your reply and help.

Do you test your example? I dont know is it problem on my side, but now socket stream dont work at all. After connect it shuts down with aiohttp exception.

Despite of my bad programming skills (and eng too :P) i tried to fix it. I think the problem consists of asyncio.run(), since it works similar to asyncio.run_until_complete() and closes the event loop calling the exception. I change the asyncio.run(main()) to: loop = asyncion.get_event_loop() loop.create_task(main()) loop.run_forever()

In this case socket.wait() can be deleted unlike case with asyncio.run(). Coz the absence of wait() allows program to finish code and close event loop.

The new question is: Is it possible to mark socket streams with corresponding match_ids? Since the callback data dont include this value. It helps one to understand which match this callback belongs to. Probably it can be realised by namespaces (from socket-io python library docs)

One more time, thx for help and sorry for bad eng.

bombsimon commented 3 years ago

Yes, I always test with examples/kill_log.py. This is the output from me running this now:

$ PYTHONPATH=. python examples/kill_log.py https://www.hltv.org/matches/2352076/galaxy-racer-vs-saw-pinnacle-fall-series-2 
connected!
running in background
stadodo (SAW) 🔫💀 chawzyyy (Galaxy Racer) with ak47
running in background
running in background

---
Round won by SAW. Score is now SAW 5 - 1 Galaxy Racer
---

running in background

Maybe you can have a look at that file to get some input on how to use async callbacks?

I don't know a way of watching multiple streams right now, I would say that would be a feature request we can track in a separate issue.

Soulbadguy54 commented 3 years ago

Oh, i understood. Your example works fine while the background function has infinite while True cycle. I changed it for finite job and this broke the program by premature end of event loop

I will check the possible way to separate the callback data and than will open new issue :)

Thx you

bombsimon commented 3 years ago

I've been working on a solution where I send the list_id as an argument to each callback function. This way you can see in the callback (if you use the same for multiple instances) which one it is. I was thinking something like this:

diff --git a/examples/kill_log.py b/examples/kill_log.py
index 810acbf..d6b670a 100644
--- a/examples/kill_log.py
+++ b/examples/kill_log.py
@@ -20,23 +20,27 @@ class MyKillFeed:
         self.scoreboard = None

     @classmethod
-    async def on_connect(cls):
+    async def on_connect(cls, list_id):
         """
         A simple callback to ensure we got connected.
         """
-        print("connected!")
+        print("connected to match {}!".format(list_id))

-    async def on_scoreboard(self, scoreboard):
+    async def on_scoreboard(self, scoreboard, list_id):
         """
         Update the scoreboard on each scoreboard event.
         """
+        del list_id
+
         self.scoreboard = scoreboard

     @classmethod
-    async def on_kill(cls, data):
+    async def on_kill(cls, data, list_id):
         """
         Print a log to the kill feed for each frag.
         """
+        del list_id
+
         assister = (
             " + {}".format(data.assister.name)
             if data.assister is not None
@@ -61,11 +65,13 @@ class MyKillFeed:
             )
         )

-    async def on_round_end(self, data):
+    async def on_round_end(self, data, list_id):
         """
         Print the team who one the round nad the current score each time around
         is over.
         """
+        del list_id
+
         winning_team = (
             self.scoreboard.terrorists
             if data["winner"] == Livescore.TEAM_TERRORIST
diff --git a/scorebot/scorebot.py b/scorebot/scorebot.py
index 14bf803..4500cac 100644
--- a/scorebot/scorebot.py
+++ b/scorebot/scorebot.py
@@ -129,14 +129,14 @@ class Livescore:
             assist = self.last_assist.pop(kill.event_id)
             kill.assister = assist["assister"]

-        await self.on_event[self.EVENT_KILL](kill)
+        await self.on_event[self.EVENT_KILL](kill, list_id=self.list_id)

     async def socket(self):
         """
         This method will return the full socket setup including all required
         event listeners for the HLTV Socket.IO.
         """
-        sio = socketio.AsyncClient()
+        sio = socketio.AsyncClient(logger=True, engineio_logger=True)

         @sio.event
         # pylint: disable=W0612
@@ -149,7 +149,7 @@ class Livescore:
             ready_data = {"listId": self.list_id}
             await sio.emit("readyForMatch", json.dumps(ready_data))

-            await self.on_event[self.EVENT_CONNECT]()
+            await self.on_event[self.EVENT_CONNECT](list_id=self.list_id)

         @sio.event
         # pylint: disable=W0612
@@ -158,7 +158,7 @@ class Livescore:
             Called when a proper disconnect is done. Will dispatch an empty
             callback for EVENT_DISCONNECT.
             """
-            await self.on_event[self.EVENT_DISCONNECT]()
+            await self.on_event[self.EVENT_DISCONNECT](list_id=self.list_id)

         @sio.event
         # pylint: disable=W0612
@@ -187,7 +187,7 @@ class Livescore:

             # We skip this playback and wait for the events to come one by one.
             if len(logs) > 1:
-                await self.on_event[self.EVENT_PLAYBACK](logs)
+                await self.on_event[self.EVENT_PLAYBACK](logs, list_id=self.list_id)
                 return

             for events in logs:
@@ -212,7 +212,7 @@ class Livescore:
                                 event_data["flasherNick"]
                             )

-                        await self.wait_for_assist(kill_event)
+                        await self.wait_for_assist(kill_event, list_id=self.list_id)
                     elif event == self.EVENT_ASSIST:
                         self.last_assist[event_data["killEventId"]] = {
                             "assister": self.get_player_by_nick(
@@ -223,9 +223,9 @@ class Livescore:
                             ),
                         }

-                        await self.on_event[self.EVENT_ASSIST](event_data)
+                        await self.on_event[self.EVENT_ASSIST](event_data, list_id=self.list_id)
                     elif event in self.on_event:
-                        await self.on_event[event](event_data)
+                        await self.on_event[event](event_data, list_id=self.list_id)
                     else:
                         print("Uncaught event: {!s}".format(event))

@@ -305,7 +305,7 @@ class Livescore:
                 counter_terrorists=team_counter_terrorist,
             )

-            await self.on_event[self.EVENT_SCOREBOARD](scoreboard_data)
+            await self.on_event[self.EVENT_SCOREBOARD](scoreboard_data, list_id=self.list_id)

         # Connect to the scorebot URI.
         await sio.connect(self.socket_uri, transports="websocket")

Would that help you with your issue?

I'm also looking into managing multiple loops better with or without other tasks running in a busy loop but I don't have an obvious solution to that yet.

Soulbadguy54 commented 3 years ago

Yes, thx for help. It helps a lot. Did you face the "packet queue is empty. Aborting" error?

And unstable connection. Every 10-30 seconds i get disconnection event and autoreconnection. Why is it so unstable? Should we try to use sync socketio?

bombsimon commented 2 years ago

Cool! I've pushed a change now sending list_id to all callbacks.

I've seen packet queue empty a few times but not nearly as often as you're describing. Usually I see it if I don't handle SIGINT properly and the server disconnects. When this happens, the socket will reconnect automatically as well.

I don't wanna make this sync again, this was changed to async in #2. I think it's more flexible to use the async version so we can allow both async/await and threading.

bombsimon commented 2 years ago

Now that we do support async callbacks and also passes the list_id, would you mind closing this issue?

If you want to troubleshoot instability I think that's better to do in a separate issue with some reproducible examples and benchmarks. I'm running the example in this repository when I test and I don't get disconnected as often.

Soulbadguy54 commented 2 years ago

Thx for help and response. Can you answer me, what is SiGINT? How can i handle it properly?

bombsimon commented 2 years ago

SIGINT is a Signal of the type INT (interrupt), usually what is sent to a process when you hit Ctrl + C.

bombsimon commented 2 years ago

Closing this, please open a new issue if you have other feature requests or if you suspect any bugs!