5
5
(i.e. the Project Slippi fork of Nintendont or Slippi Ishiiruka).
6
6
"""
7
7
8
- import socket
8
+ from audioop import add
9
9
from enum import Enum
10
10
import enet
11
11
import json
12
+ import multiprocessing as mp
13
+ from multiprocessing .connection import Connection
14
+ from multiprocessing .synchronize import Event
12
15
13
16
# pylint: disable=too-few-public-methods
14
17
class EventType (Enum ):
@@ -30,88 +33,161 @@ class CommType(Enum):
30
33
KEEPALIVE = 0x03
31
34
MENU = 0x04
32
35
33
- class SlippstreamClient ():
34
- """ Container representing a client to some SlippiComm server """
35
36
36
- def __init__ (self , address = "127.0.0.1" , port = 51441 , realtime = True ):
37
- """ Constructor for this object """
37
+ class SlippstreamWorker :
38
+ def __init__ (
39
+ self ,
40
+ address : str ,
41
+ port : int ,
42
+ buffer : Connection ,
43
+ shutdown : Event ,
44
+ ):
45
+ self .address = address
46
+ self .port = port
47
+ self ._buffer = buffer
48
+ self ._shutdown = shutdown
49
+
38
50
self ._host = enet .Host (None , 1 , 0 , 0 )
39
51
self ._peer = None
40
- self .buf = bytearray ()
41
- self .realtime = realtime
52
+
53
+ self ._handshake_data = json .dumps ({
54
+ "type" : "connect_request" ,
55
+ "cursor" : 0 ,
56
+ }).encode ()
57
+
58
+ def _send_handshake (self ):
59
+ self ._peer .send (0 , enet .Packet (self ._handshake_data ))
60
+
61
+ def connect (self ) -> bool :
62
+ """Connect to the server
63
+
64
+ Returns True on success, False on failure
65
+ """
66
+ # Try to connect to the server and send a handshake
67
+ try :
68
+ self ._peer = self ._host .connect (
69
+ enet .Address (bytes (self .address , 'utf-8' ), self .port ), 1 )
70
+ except OSError :
71
+ return False
72
+ try :
73
+ for _ in range (4 ):
74
+ event = self ._host .service (1000 )
75
+ if event .type == enet .EVENT_TYPE_CONNECT :
76
+ self ._send_handshake ()
77
+ return True
78
+ return False
79
+ except OSError :
80
+ return False
81
+
82
+ def run (self ):
83
+ connected = self .connect ()
84
+ self ._buffer .send (connected )
85
+ if not connected :
86
+ return
87
+
88
+ while not self ._shutdown .is_set ():
89
+ event = self ._host .service (1000 )
90
+
91
+ if event .type == enet .EVENT_TYPE_NONE :
92
+ continue # timeout
93
+ elif event .type == enet .EVENT_TYPE_RECEIVE :
94
+ self ._buffer .send_bytes (event .packet .data )
95
+ elif event .type == enet .EVENT_TYPE_CONNECT :
96
+ # should this happen during the run loop?
97
+ self ._send_handshake ()
98
+ elif event .type == enet .EVENT_TYPE_DISCONNECT :
99
+ self ._buffer .close ()
100
+ return
101
+
102
+ def _run_worker (** kwargs ):
103
+ SlippstreamWorker (** kwargs ).run ()
104
+
105
+ class EnetDisconnected (Exception ):
106
+ """Raised when we get an enet disconnection."""
107
+
108
+ class SlippstreamClient :
109
+ """ Container representing a client to some SlippiComm server """
110
+
111
+ def __init__ (
112
+ self ,
113
+ address = "127.0.0.1" ,
114
+ port = 51441 ,
115
+ ):
42
116
self .address = address
43
117
self .port = port
118
+ self .running = False
119
+
120
+ # set up worker process
121
+ self ._buffer , worker_buffer = mp .Pipe (False )
122
+ self ._shutdown = mp .Event ()
123
+ self ._worker = mp .Process (
124
+ target = _run_worker ,
125
+ kwargs = dict (
126
+ address = address ,
127
+ port = port ,
128
+ buffer = worker_buffer ,
129
+ shutdown = self ._shutdown ,
130
+ )
131
+ )
132
+
44
133
# Not yet supported
45
134
self .playedOn = "dolphin"
46
135
self .timestamp = ""
47
136
self .consoleNick = ""
48
137
self .players = {}
49
138
50
- def shutdown (self ):
51
- """ Close down the socket and connection to the console """
52
- if self ._peer :
53
- self ._peer .send (0 , enet .Packet ())
54
- self ._host .service (100 )
55
- self ._peer .disconnect ()
56
- self ._peer = None
57
-
58
- if self ._host :
59
- self ._host = None
60
- return False
61
-
62
- def dispatch (self , polling_mode ):
63
- """Dispatch messages with the peer (read and write packets)"""
64
- event = None
65
- event_type = 1000
66
- while event_type not in [enet .EVENT_TYPE_RECEIVE ]:
67
- wait_time = 1000
68
- if polling_mode :
69
- wait_time = 0
70
- event = self ._host .service (wait_time )
71
- event_type = event .type
139
+ def _read_messages (self ):
140
+ while self .running :
141
+ event = self ._host .service (1000 )
72
142
73
143
if event .type == enet .EVENT_TYPE_NONE :
74
- if polling_mode :
75
- return None
76
- if event .type == enet .EVENT_TYPE_RECEIVE :
144
+ continue # timeout
145
+ elif event .type == enet .EVENT_TYPE_RECEIVE :
77
146
try :
78
- return json .loads (event .packet .data )
147
+ message = json .loads (event .packet .data )
148
+ print (message ["type" ])
149
+ self ._buffer .put (message )
79
150
except json .JSONDecodeError :
80
151
# This happens at the end of a game for some reason?
81
152
if len (event .packet .data ) == 0 :
82
- event_type = 0
153
+ # TODO: figure out what to do in this case
83
154
continue
84
- return None
85
155
elif event .type == enet .EVENT_TYPE_CONNECT :
86
156
handshake = json .dumps ({
87
157
"type" : "connect_request" ,
88
158
"cursor" : 0 ,
89
159
})
90
160
self ._peer .send (0 , enet .Packet (handshake .encode ()))
91
161
elif event .type == enet .EVENT_TYPE_DISCONNECT :
92
- return None
93
- return None
162
+ raise RuntimeError ('enet disconnected' )
94
163
95
- def connect (self ):
96
- """ Connect to the server
164
+ def shutdown (self ):
165
+ """ Close down the socket and connection to the console """
166
+ if self ._worker :
167
+ self ._shutdown .set ()
168
+ self ._worker .join ()
169
+ self ._buffer .close ()
170
+ self ._worker = None
171
+ self .running = False
172
+
173
+ def dispatch (self , polling_mode : bool ):
174
+ """Dispatch messages with the peer (read and write packets)"""
175
+ assert self .running , "Can only dispatch while running."
97
176
98
- Returns True on success, False on failure
99
- """
100
- # Try to connect to the server and send a handshake
101
- try :
102
- self ._peer = self ._host .connect (enet .Address (bytes (self .address , 'utf-8' ), int (self .port )), 1 )
103
- except OSError :
104
- return False
105
177
try :
106
- for _ in range (4 ):
107
- event = self ._host .service (1000 )
108
- if event .type == enet .EVENT_TYPE_CONNECT :
109
- handshake = json .dumps ({
110
- "type" : "connect_request" ,
111
- "cursor" : 0 ,
112
- })
113
- self ._peer .send (0 , enet .Packet (handshake .encode ()))
114
- return True
115
- return False
116
- except OSError :
117
- return False
178
+ if polling_mode and not self ._buffer .poll ():
179
+ return None
180
+ message_bytes = self ._buffer .recv_bytes ()
181
+ except EOFError :
182
+ raise EnetDisconnected ()
183
+
184
+ return json .loads (message_bytes )
185
+
186
+ def connect (self ) -> bool :
187
+ self ._worker .start ()
188
+ connected = self ._buffer .recv ()
189
+ if not connected :
190
+ self .shutdown ()
191
+ else :
192
+ self .running = True
193
+ return connected
0 commit comments