1- import time
21from collections import deque
32from threading import Thread
3+ import time
4+
5+ from hearthstone .enums import GameType , FormatType
46
57from hslog import packets , tokens
8+ from hslog .exceptions import RegexParsingError
69from hslog .live .packets import LivePacketTree
10+ from hslog .live .player import LivePlayerManager
711from hslog .parser import LogParser
8- from hslog .player import LazyPlayer , PlayerManager
9- from hslog .utils import parse_tag
12+ from hslog .player import LazyPlayer
13+ from hslog .utils import parse_tag , parse_enum
1014
1115
1216class LiveLogParser (LogParser ):
17+ """
18+ LiveLogParser adds live log translation into useful data.
19+
20+ Lines are read and pushed into a deque by a separate thread.
21+ Deque is emptied by parse_worker which replaces the read()
22+ function of LogParser and it's also in a separate thread.
23+
24+ This approach is non-blocking and allows for live parsing
25+ of incoming lines.
26+ """
1327
1428 def __init__ (self , filepath ):
1529 super (LiveLogParser , self ).__init__ ()
@@ -18,55 +32,98 @@ def __init__(self, filepath):
1832 self .lines_deque = deque ([])
1933
2034 def new_packet_tree (self , ts ):
35+ """
36+ LivePacketTree is introduced here because it instantiates LiveEntityTreeExporter
37+ and keeps track of the parser parent. It also contains a function that
38+ utilizes the liveExporter instance across all the games.
39+
40+ self.parser = parser
41+ self.liveExporter = LiveEntityTreeExporter(self)
42+ """
2143 self ._packets = LivePacketTree (ts , self )
2244 self ._packets .spectator_mode = self .spectator_mode
23- self ._packets .manager = PlayerManager ()
45+ self ._packets .manager = LivePlayerManager ()
2446 self .current_block = self ._packets
2547 self .games .append (self ._packets )
2648
27- """ why is this return important? """
49+ """
50+ why is this return important?
51+ it's called only here:
52+
53+ def create_game(self, ts):
54+ self.new_packet_tree(ts)
55+ """
2856 return self ._packets
57+
58+ def handle_game (self , ts , data ):
59+ if data .startswith ("PlayerID=" ):
60+ sre = tokens .GAME_PLAYER_META .match (data )
61+ if not sre :
62+ raise RegexParsingError (data )
63+ player_id , player_name = sre .groups ()
64+
65+ # set the name of the player
66+ players = self .games [- 1 ].liveExporter .game .players
67+ for p in players :
68+ if p .player_id == int (player_id ): p .name = player_name
69+
70+ player_id = int (player_id )
71+ else :
72+ key , value = data .split ("=" )
73+ key = key .strip ()
74+ value = value .strip ()
75+ if key == "GameType" :
76+ value = parse_enum (GameType , value )
77+ elif key == "FormatType" :
78+ value = parse_enum (FormatType , value )
79+ else :
80+ value = int (value )
2981
82+ self .game_meta [key ] = value
83+
3084 def tag_change (self , ts , e , tag , value , def_change ):
3185 entity_id = self .parse_entity_or_player (e )
3286 tag , value = parse_tag (tag , value )
3387 self ._check_for_mulligan_hack (ts , tag , value )
3488
35- """ skipping LazyPlayer here because it doesn"t have data """
36- skip = False
3789 if isinstance (entity_id , LazyPlayer ):
38- entity_id = self ._packets .manager .register_player_name_on_tag_change (
39- entity_id , tag , value
40- )
41- skip = True
90+ entity_id = self ._packets .manager .register_player_name_on_tag_change (entity_id , tag , value )
91+
4292 has_change_def = def_change == tokens .DEF_CHANGE
4393 packet = packets .TagChange (ts , entity_id , tag , value , has_change_def )
44-
45- if not skip :
46- self .register_packet (packet )
94+ if entity_id : self .register_packet (packet )
4795 return packet
4896
4997 def register_packet (self , packet , node = None ):
50- """ make sure we"re registering packets to the current game"""
98+ """
99+ LogParser.register_packet override
100+
101+ This uses the live_export functionality introduces by LivePacketTree
102+ It also keeps track of which LivePacketTree is being used when there
103+ are multiple in parser.games
104+
105+ A better naming for a PacketTree/LivePacketTree would be HearthstoneGame?
106+ Then parser.games would contain HearthstoneGame instances and would
107+ be more obvious what the purpose is.
108+ """
109+
110+ # make sure we're registering packets to the current game
51111 if not self ._packets or self ._packets != self .games [- 1 ]:
52112 self ._packets = self .games [- 1 ]
53113
54114 if node is None :
55115 node = self .current_block .packets
56116 node .append (packet )
57-
58- """ line below triggers packet export which will
59- run update_callback for entity being
60- updated by the packet.
61-
62- self._packets == EntityTreeExporter
63- """
64- self ._packets .live_export (packet )
65-
66117 self ._packets ._packet_counter += 1
67118 packet .packet_id = self ._packets ._packet_counter
119+ self ._packets .live_export (packet )
68120
69121 def file_worker (self ):
122+ """
123+ File reader thread. (Naive implementation)
124+ Reads the log file continuously and appends to deque.
125+ """
126+
70127 file = open (self .filepath , "r" )
71128 while self .running :
72129 line = file .readline ()
@@ -76,6 +133,9 @@ def file_worker(self):
76133 time .sleep (0.2 )
77134
78135 def parse_worker (self ):
136+ """
137+ If deque contains lines, this initiates parsing.
138+ """
79139 while self .running :
80140 if len (self .lines_deque ):
81141 line = self .lines_deque .popleft ()
0 commit comments