private async Task <Server[]> HandshakeAsync(bool reconnect, bool resumable, GatewaySocket socket, IRef <bool> loop) { DiscordDebug.WriteLine("Getting Hello data..."); ReceiveGatewayData data = await socket.ReadAsync(); DiscordDebug.WriteLine("Got Hello data."); if (reconnect) { _listener.UpdateSocket(socket); } else { DiscordDebug.WriteLine("Initializing response listener and heartbeater..."); _listener = new ResponseListener(socket); _heartbeater = new GatewayHeartbeater(_listener, _seqMutex, () => _lastSequence, ConnectAsync); DiscordDebug.WriteLine("Response listener and heartbeater initialized."); } // Need to handle cases where heartbeat timer elapses, thus the event/semaphore model DiscordDebug.WriteLine("Waiting for initial heartbeat ack..."); var signal = new Semaphore(0, 1); bool success = false; void EvtListener(object sender, bool e) { success = e; signal.Release(); } _heartbeater.Started += EvtListener; _heartbeater.Start(data, reconnect); signal.WaitOne(); _heartbeater.Started -= EvtListener; if (success) { DiscordDebug.WriteLine("Heartbeat ack heard."); return(await IdentAsync(reconnect, resumable)); // ReSharper disable once RedundantIfElseBlock } else { DiscordDebug.WriteLine("Heartbeat ack not heard, trying again..."); // Reconnect immediately await _heartbeater.AbortAsync(); _heartbeater.Dispose(); _listener.Dispose(); loop.Value = true; return(null); } }
private void GuildCreate(ReceiveGatewayData data) { var d = (GatewayEvent.EvtServer)data.Data; _asMutex.Wait(); try { DiscordDebug.WriteLine("GUILD_CREATE " + d.Data.Id + " (" + (_awaitServers.Count - 1) + " remaining)"); _awaitServers.Remove(d.Data.Id); if (_awaitServers.Count == 0) { _asDone.Release(); } } finally { _asMutex.Release(); } }
public static ReceiveGatewayData CreateJson(SocketData data) { ReceiveGatewayData res; if (data.MessageType == WebSocketMessageType.Close) { res = null; } else { res = new ReceiveGatewayData(); var j = JsonConvert.DeserializeObject <GatewayJson>(data.GetText()); res.OpCode = j.op; // ReSharper disable once SwitchStatementMissingSomeCases (default case exists) switch (res.OpCode) { case GatewayOpCode.Dispatch: res.Sequence = (int)j.s; res.EventName = j.t; res.Data = GatewayEvent.Create(j.t, j.d); break; case GatewayOpCode.Heartbeat: res.Data = ((JValue)j.d).Value; break; case GatewayOpCode.Hello: res.Data = new GatewayHelloData((JObject)j.d); break; case GatewayOpCode.InvalidSession: res.Data = (bool)((JValue)j.d).Value; break; case GatewayOpCode.Reconnect: case GatewayOpCode.HeartbeatAck: res.Data = null; break; default: throw new UnknownOpCodeException("Gateway op code " + (int)j.op + " is either invalid in a recieve context or is undefined."); } } return(res); }
public void Start(ReceiveGatewayData helloData, bool reconnect) { Thread starter = DiscordEnvironment.CreateSubthread(() => { if (reconnect) { _heartbeat.Stop(); _heartbeat.Dispose(); } _fire = true; //Task initBeat = HeartbeatAsync(); _heartbeat = new Timer(((GatewayHelloData)helloData.Data).HeartbeatInterval.TotalMilliseconds) { AutoReset = true, SynchronizingObject = null }; _heartbeat.Elapsed += (sender, args) => HeartbeatAsync().Await(); _heartbeat.Start(); //initBeat.Await(); _ackMutex.Wait(); if (_aborts > 0) { _fire = false; _aborts--; _ackMutex.Release(); } else { bool oldFire = _fire; _fire = false; _ackMutex.Release(); if (oldFire) { Started?.Invoke(this, true); } } }); starter.Start(); }
public static bool EventIsThis(ReceiveGatewayData data) { return(data.EventName != null && EventIsThis(data.EventName)); }
private async Task <Server[]> IdentAsync(bool reconnect, bool resumable) { bool ident = true; if (reconnect && resumable) { ReceiveGatewayData resumed = await _listener.SendAsync(SendGatewayData.Create(GatewayOpCode.Resume, MakeResume()), new[] { GatewayOpCode.Dispatch, GatewayOpCode.InvalidSession }, GatewayEvent.Resumed.Id); ident = resumed.OpCode == GatewayOpCode.InvalidSession; if (ident) { // Per specs, client must wait 1-5 seconds before sending a fresh Identify payload (https://discordapp.com/developers/docs/topics/gateway#resuming) await Task.Delay(2000); } } await _asMutex.WaitAsync(); bool needRelease = true; Server[] res = null; try { while (ident) { DiscordDebug.WriteLine("Identing..."); ReceiveGatewayData ready = await _listener.SendAsync(SendGatewayData.Create(GatewayOpCode.Identify, (JObject)MakeIdentify()), new[] { GatewayOpCode.Dispatch, GatewayOpCode.InvalidSession }, GatewayEvent.Ready.Id); ident = ready.OpCode == GatewayOpCode.InvalidSession; if (ident) { DiscordDebug.WriteLine("Ident rate-limited, waiting and trying again..."); // Per specs, the only case in which this occurs is when you hit the 5-second Identify rate limit (https://discordapp.com/developers/docs/topics/gateway#identifying) await Task.Delay(5000); } else { DiscordDebug.WriteLine("Ident successful, copying session data..."); var readyData = (GatewayEvent.Ready)ready.Data; _sessionId = readyData.SessionId; DiscordDebug.WriteLine("Session data copied."); DiscordDebug.WriteLine("Awaiting server create events (expecting [" + string.Join(", ", readyData.ServersToCreate) + "] (" + readyData.ServersToCreate.Count + "))..."); _awaitServers = readyData.ServersToCreate; _listener.Listen(GuildCreate, _awaitServers.Count, GatewayOpCode.Dispatch, GatewayEvent.EvtServer.CreateId); _asDone = new Semaphore(0, 1); _asMutex.Release(); needRelease = false; res = readyData.ServersToCreate.Select(Server.GetCached).ToArray(); _asDone.WaitOne(); DiscordDebug.WriteLine("All expected server create events have occured."); } DiscordDebug.WriteLine("Ident done."); } } finally { if (needRelease) { _asMutex.Release(); } } DiscordDebug.WriteLine("Setting up event listener..."); _dispatcher = new Dispatcher(this, _listener); DiscordDebug.WriteLine("Event listener set up."); DiscordDebug.WriteLine("Socket ready to use"); // ReSharper disable once AssignNullToNotNullAttribute return(res); }