using System; using System.Collections.Generic; using Sandbox.ModAPI.Ingame; using VRage.Game.ModAPI.Ingame.Utilities; namespace AutoMiningScript { public partial class Program : MyGridProgram { public class Packet { public string Kind = "", From = "", Target = "", Id = "", Session = ""; public long Seq, Source, Epoch; public bool Reliable; public MyIni Body = new MyIni(); public string Encode(string fleet) { var i = new MyIni(); i.Set("wire", "Version", 2); i.Set("wire", "Epoch", Epoch); i.Set("wire", "Fleet", fleet); i.Set("wire", "Kind", Kind); i.Set("wire", "From", From); i.Set("wire", "Target", Target); i.Set("wire", "Id", Id); i.Set("wire", "Session", Session); i.Set("wire", "Seq", Seq); i.Set("wire", "Reliable", Reliable); i.Set("wire", "Payload", Body.ToString()); return i.ToString(); } public static bool TryDecode(string text, string fleet, out Packet packet) { packet = null; if (text == null || text.Length > 24000) return false; try { var i = new MyIni(); if (!i.TryParse(text) || Data.ReadLong(i, "wire", "Version") != 2 || Data.Text(i,"wire", "Fleet") != fleet) return false; var p = new Packet { Kind = Data.Text(i,"wire", "Kind"), From = Data.Text(i,"wire", "From"), Target = Data.Text(i,"wire", "Target"), Id = Data.Text(i,"wire", "Id"), Session = Data.Text(i,"wire", "Session"), Seq = Data.ReadLong(i, "wire", "Seq"), Reliable = Data.Flag(i,"wire", "Reliable") }; if (p.From.Length == 0 || p.From.Length > 64 || p.Kind.Length == 0 || p.Kind.Length > 32 || p.Id.Length > 160 || p.Session.Length == 0 || p.Session.Length > 160 || p.Seq < 1 || !p.Body.TryParse(Data.Text(i,"wire", "Payload"))) return false; p.Epoch=Data.ReadLong(i,"wire","Epoch");if(p.Epoch<0 || p.Epoch>DateTime.MaxValue.Ticks)return false; packet = p; return true; } catch (ArgumentException) { return false; } } } public class Wire { class Pending { public Packet Packet; public long Address; public double Sent; public int Tries; } readonly Program p; IMyBroadcastListener broadcast; string tag, session; readonly Dictionary> streams = new Dictionary>(); readonly Dictionary pending = new Dictionary(); readonly Dictionary peers = new Dictionary(); readonly Dictionary protectedPeers = new Dictionary(); readonly Dictionary peerSessions = new Dictionary(); readonly HashSet retired = new HashSet(); readonly Queue retiredOrder = new Queue(); readonly Queue forgotten = new Queue(); long sequence; int retryCursor; public string Session => session; public long Epoch; public int PendingCount => pending.Count; public void ProtectPeer(string id, long address) { if (address != 0) protectedPeers[id] = address; else protectedPeers.Remove(id); } public void CancelPending(string kind, string target = null) { var keys = new List(pending.Keys); foreach (var key in keys) { var packet = pending[key].Packet; if ((kind == null || packet.Kind == kind) && (target == null || packet.Target == target)) pending.Remove(key); } } public void ForgetPeer(string id) { CancelPending(null, id); peers.Remove(id); // Keep a bounded replay window after releasing the address slot. New // telemetry can register again; already processed packets cannot do so. forgotten.Enqueue(id); while (forgotten.Count > 128) { var old = forgotten.Dequeue(); if (peers.ContainsKey(old) || forgotten.Contains(old)) continue; streams.Remove(old); peerSessions.Remove(old); } } public Wire(Program owner) { p = owner;Reset(); } public void Reset() { var channel="AMS2/"+p.Config.FleetId; if(broadcast==null || tag!=channel) { if(broadcast!=null)p.IGC.DisableBroadcastListener(broadcast); tag=channel;broadcast=p.IGC.RegisterBroadcastListener(tag);broadcast.SetMessageCallback("igc");p.IGC.UnicastListener.SetMessageCallback("igc"); } pending.Clear();peers.Clear();protectedPeers.Clear();streams.Clear();peerSessions.Clear();retired.Clear();retiredOrder.Clear();forgotten.Clear();retryCursor=0;sequence=0; session=p.Me.EntityId.ToString()+"-"+DateTime.UtcNow.Ticks.ToString();Epoch=0; } public void Send(string kind, string target, MyIni body, bool reliable = false, long address = 0) { var packet = new Packet { Kind = kind, From = p.Config.Id, Target = target ?? "", Session = session, Seq = ++sequence, Body = body ?? new MyIni(), Reliable = reliable }; packet.Epoch=Epoch; packet.Id = session + "-" + packet.Seq; if (address == 0 && packet.Target.Length > 0) peers.TryGetValue(packet.Target, out address); if (reliable && packet.Target.Length == 0) throw Data.Invalid(L.CommonReliableTarget); if (reliable && pending.Count >= 64) { p.Log(L.F(L.CommonReliableQueue, kind)); return; } Transmit(packet, address); if (reliable) pending[packet.Id] = new Pending { Packet = packet, Address = address, Sent = p.Now, Tries = 1 }; } void Transmit(Packet packet, long address) { var text = packet.Encode(p.Config.FleetId); if (text.Length > 24000) throw Data.Invalid(L.CommonMessageBudget); if (address != 0) p.IGC.SendUnicastMessage(address, tag, text); else p.IGC.SendBroadcastMessage(tag, text, TransmissionDistance.TransmissionDistanceMax); } public void Tick() { if (!p.HasBudget(0.55)) return; var keys = new List(pending.Keys); int sent = 0; for (int n = 0; n < keys.Count && sent < 2 && p.HasBudget(0.6); n++) { retryCursor %= keys.Count; var key = keys[retryCursor++]; var item = pending[key]; if (p.Now - item.Sent < 1.5) continue; if (item.Tries >= 6) { p.Log(L.F(L.CommonNoAck, item.Packet.Kind, item.Packet.Target)); pending.Remove(key); continue; } if (item.Address == 0) peers.TryGetValue(item.Packet.Target, out item.Address); Transmit(item.Packet, item.Address); item.Sent = p.Now; item.Tries++; sent++; } } public void Drain(Action receive) { int budget = 4, start = p.Runtime.CurrentInstructionCount; while (budget-- > 0 && p.HasBudget(0.55) && p.Runtime.CurrentInstructionCount - start < p.Runtime.MaxInstructionCount * 0.2 && (broadcast.HasPendingMessage || p.IGC.UnicastListener.HasPendingMessage)) { var message = p.IGC.UnicastListener.HasPendingMessage ? p.IGC.UnicastListener.AcceptMessage() : broadcast.AcceptMessage(); if (message.Tag != tag) continue; Packet packet; if (!Packet.TryDecode(message.Data as string, p.Config.FleetId, out packet) || packet.From == p.Config.Id || (packet.Target.Length > 0 && packet.Target != p.Config.Id)) continue; packet.Source = message.Source; long protectedAddress; if (protectedPeers.TryGetValue(packet.From, out protectedAddress) && protectedAddress != packet.Source) continue; if(p.Config.Role=="fleet" && packet.Epoch!=Epoch) { try {receive(packet);}catch(ArgumentException e){p.Log(L.F(L.CommonRejectedPacket,packet.Kind,e.Message));} continue; } if(packet.From==p.Config.BaseId && packet.Epoch forgottenStreams; long forgottenSeq; if (!peers.ContainsKey(packet.From) && peerSessions.TryGetValue(packet.From, out forgottenSession) && forgottenSession == packet.Session && streams.TryGetValue(packet.From, out forgottenStreams) && forgottenStreams.TryGetValue(packet.Kind, out forgottenSeq) && packet.Seq <= forgottenSeq) { if (packet.Reliable) { var ack = new MyIni(); ack.Set("ack", "Id", packet.Id); Send("ACK", packet.From, ack, false, message.Source); } continue; } long known; if (peers.TryGetValue(packet.From, out known) && known != message.Source) { p.Log(L.F(L.CommonDuplicateNode, packet.From)); continue; } if (peers.Count >= 64 && !peers.ContainsKey(packet.From)) continue; string oldSession; if (peerSessions.TryGetValue(packet.From, out oldSession) && oldSession != packet.Session) { var retiredKey = packet.From + "/" + oldSession; if (retired.Add(retiredKey)) retiredOrder.Enqueue(retiredKey); while (retiredOrder.Count > 1024) retired.Remove(retiredOrder.Dequeue()); // Session replacement automatically drops obsolete sequence-cache entries. // Retired session identities still reject late/replayed old packets. streams.Remove(packet.From); } peers[packet.From] = message.Source; peerSessions[packet.From] = packet.Session; Dictionary peerStreams; if (!streams.TryGetValue(packet.From, out peerStreams)) { peerStreams = new Dictionary(); streams[packet.From] = peerStreams; } long seen; var duplicate = peerStreams.TryGetValue(packet.Kind, out seen) && packet.Seq <= seen; if (!duplicate && peerStreams.Count >= 32 && !peerStreams.ContainsKey(packet.Kind)) { p.Log(L.CommonStreamLimit); continue; } if (packet.Reliable) { var ack = new MyIni(); ack.Set("ack", "Id", packet.Id); Send("ACK", packet.From, ack, false, message.Source); } if (duplicate) continue; peerStreams[packet.Kind] = packet.Seq; try { receive(packet); } catch (ArgumentException e) { p.Log(L.F(L.CommonRejectedPacket, packet.Kind, e.Message)); } } } } } }