22from collections import deque
33from threading import Thread
44
5+ from hearthstone .enums import FormatType , GameType
6+
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_enum , parse_tag
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,101 @@ 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
2957
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 ):
69+ p .name = player_name
70+
71+ player_id = int (player_id )
72+ else :
73+ key , value = data .split ("=" )
74+ key = key .strip ()
75+ value = value .strip ()
76+ if key == "GameType" :
77+ value = parse_enum (GameType , value )
78+ elif key == "FormatType" :
79+ value = parse_enum (FormatType , value )
80+ else :
81+ value = int (value )
82+
83+ self .game_meta [key ] = value
84+
3085 def tag_change (self , ts , e , tag , value , def_change ):
3186 entity_id = self .parse_entity_or_player (e )
3287 tag , value = parse_tag (tag , value )
3388 self ._check_for_mulligan_hack (ts , tag , value )
3489
35- """ skipping LazyPlayer here because it doesn"t have data """
36- skip = False
3790 if isinstance (entity_id , LazyPlayer ):
3891 entity_id = self ._packets .manager .register_player_name_on_tag_change (
39- entity_id , tag , value
40- )
41- skip = True
92+ entity_id , tag , value )
93+
4294 has_change_def = def_change == tokens .DEF_CHANGE
4395 packet = packets .TagChange (ts , entity_id , tag , value , has_change_def )
44-
45- if not skip :
96+ if entity_id :
4697 self .register_packet (packet )
4798 return packet
4899
49100 def register_packet (self , packet , node = None ):
50- """ make sure we"re registering packets to the current game"""
101+ """
102+ LogParser.register_packet override
103+
104+ This uses the live_export functionality introduces by LivePacketTree
105+ It also keeps track of which LivePacketTree is being used when there
106+ are multiple in parser.games
107+
108+ A better naming for a PacketTree/LivePacketTree would be HearthstoneGame?
109+ Then parser.games would contain HearthstoneGame instances and would
110+ be more obvious what the purpose is.
111+ """
112+
113+ # make sure we"re registering packets to the current game
51114 if not self ._packets or self ._packets != self .games [- 1 ]:
52115 self ._packets = self .games [- 1 ]
53116
54117 if node is None :
55118 node = self .current_block .packets
56119 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-
66120 self ._packets ._packet_counter += 1
67121 packet .packet_id = self ._packets ._packet_counter
122+ self ._packets .live_export (packet )
68123
69124 def file_worker (self ):
125+ """
126+ File reader thread. (Naive implementation)
127+ Reads the log file continuously and appends to deque.
128+ """
129+
70130 file = open (self .filepath , "r" )
71131 while self .running :
72132 line = file .readline ()
@@ -76,6 +136,9 @@ def file_worker(self):
76136 time .sleep (0.2 )
77137
78138 def parse_worker (self ):
139+ """
140+ If deque contains lines, this initiates parsing.
141+ """
79142 while self .running :
80143 if len (self .lines_deque ):
81144 line = self .lines_deque .popleft ()
0 commit comments