-
Notifications
You must be signed in to change notification settings - Fork 13
Open
Description
Describe the bug
The websocket limits only one coroutine to await on recv to prevent racing problem. However current pyvts introduces blocking behavior in
Lines 117 to 118 in 3c5fb84
| await self.websocket.send(json.dumps(request_msg)) | |
| response_msg = await self.websocket.recv() |
Because every spawned coroutine will await on the same websocket object and cause error.
To Reproduce
Steps to reproduce the behavior:
- Send multiple requests at once using
asyncio.gather. RuntimeError: cannot call recv while another coroutine is already waiting for the next message
Expected behavior
Requests should be fully async no matter how many requests are being sent at once.
Desktop (please complete the following information):
- OS: Windows
- Version: 0.3.3
Additional context
I have a draft implementation of a full async WS event handler here (though it's very not following the original implementation of vts):
class vts:
"""
A fully async implementation of VTube Studio API
"""
ws: WebSocketClientProtocol
requests: dict[str, Event]
def __init__(
self,
name: str,
developer: str,
icon: str = None,
auth_token: str = None,
ip: str = "localhost",
port: int = 8001,
) -> None:
self.auth_info = {
"pluginName": name,
"pluginDeveloper": developer,
"pluginIcon": icon,
}
self.auth_token = auth_token
self.ip = ip
self.port = port
self.ws = None
self.requests = {}
async def connect(self) -> None:
if self.ws is not None:
raise RuntimeError("Already connected")
self.ws = await websockets.connect(f"ws://{self.ip}:{self.port}")
self.recv_loop = asyncio.create_task(self.run_recv_loop())
async def run_recv_loop(self) -> None:
"""
Only the recv loop should read from the websocket.
We identify each incoming response by the requestID.
"""
async for message in self.ws:
resp = json.loads(message)
id = resp["requestID"]
event = self.requests.pop(id)
if event is not None:
setattr(event, "data", resp.get("data", None))
event.set() # Notify the request is done
async def request(
self,
command: str,
payload: dict[str, Any] = None,
id: str = None,
) -> None:
"""
Send a request to the server. This does not throw if there are
multiple requests.
"""
if id is None:
id = str(uuid4())
apiPayload = {
"apiName": "VTubeStudioPublicAPI",
"apiVersion": "1.0",
"requestID": id,
"messageType": command,
}
if payload is not None:
apiPayload["payload"] = payload
event = Event() # We make a signal here to wait for the response
self.requests[id] = event
await self.ws.send(json.dumps(apiPayload))
await event.wait()
return getattr(event, "data", None)
async def close(self) -> None:
self.recv_loop.cancel()
await self.ws.close()
self.ws = None
self.requests = {}Prooticat
Metadata
Metadata
Assignees
Labels
No labels