using System; using System.IO; using System.Text; using FloodSWE.Streaming; using FloodSWE.TileGraph; using UnityEngine; namespace FloodSWE.Networking { public static class SweUdpProtocol { public const byte FrameType = 1; public const byte ControlType = 2; public const byte AckType = 3; public const byte StateType = 4; public static byte[] EncodeFrame(HeightmapPacket packet) { using (var stream = new MemoryStream(64 + (packet.Payload?.Length ?? 0))) using (var writer = new BinaryWriter(stream, Encoding.UTF8, true)) { writer.Write(FrameType); writer.Write(packet.FrameId); writer.Write(packet.Tile.Lod); writer.Write(packet.Tile.X); writer.Write(packet.Tile.Y); writer.Write(packet.Resolution); writer.Write(packet.MinHeight); writer.Write(packet.MaxHeight); int length = packet.Payload != null ? packet.Payload.Length : 0; writer.Write(length); if (length > 0) { writer.Write(packet.Payload); } writer.Flush(); return stream.ToArray(); } } public static bool TryDecodeFrame(byte[] data, out HeightmapPacket packet) { packet = default; if (data == null || data.Length < 32) { return false; } try { using (var stream = new MemoryStream(data)) using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) { if (reader.ReadByte() != FrameType) { return false; } int frameId = reader.ReadInt32(); int lod = reader.ReadInt32(); int x = reader.ReadInt32(); int y = reader.ReadInt32(); int resolution = reader.ReadInt32(); float minHeight = reader.ReadSingle(); float maxHeight = reader.ReadSingle(); int payloadLen = reader.ReadInt32(); if (payloadLen <= 0 || payloadLen > data.Length) { return false; } byte[] payload = reader.ReadBytes(payloadLen); if (payload.Length != payloadLen) { return false; } packet = new HeightmapPacket { FrameId = frameId, Tile = new TileId(lod, x, y), Resolution = resolution, MinHeight = minHeight, MaxHeight = maxHeight, Payload = payload, }; return packet.IsValid; } } catch { return false; } } public static byte[] EncodeControl(SweControlCommand command) { string json = JsonUtility.ToJson(command); byte[] utf8 = Encoding.UTF8.GetBytes(json); byte[] payload = new byte[utf8.Length + 1]; payload[0] = ControlType; Buffer.BlockCopy(utf8, 0, payload, 1, utf8.Length); return payload; } public static bool TryDecodeControl(byte[] data, out SweControlCommand command) { command = null; if (data == null || data.Length < 2 || data[0] != ControlType) { return false; } try { string json = Encoding.UTF8.GetString(data, 1, data.Length - 1); command = JsonUtility.FromJson(json); return command != null; } catch { return false; } } public static byte[] EncodeAck(string message) { byte[] utf8 = Encoding.UTF8.GetBytes(message ?? string.Empty); byte[] payload = new byte[utf8.Length + 1]; payload[0] = AckType; Buffer.BlockCopy(utf8, 0, payload, 1, utf8.Length); return payload; } public static bool TryDecodeAck(byte[] data, out string message) { message = null; if (data == null || data.Length < 1 || data[0] != AckType) { return false; } try { message = data.Length == 1 ? string.Empty : Encoding.UTF8.GetString(data, 1, data.Length - 1); return true; } catch { message = null; return false; } } public static byte[] EncodeState(SweBoundaryStateMessage message) { string json = JsonUtility.ToJson(message); byte[] utf8 = Encoding.UTF8.GetBytes(json); byte[] payload = new byte[utf8.Length + 1]; payload[0] = StateType; Buffer.BlockCopy(utf8, 0, payload, 1, utf8.Length); return payload; } public static bool TryDecodeState(byte[] data, out SweBoundaryStateMessage message) { message = null; if (data == null || data.Length < 2 || data[0] != StateType) { return false; } try { string json = Encoding.UTF8.GetString(data, 1, data.Length - 1); message = JsonUtility.FromJson(json); return message != null; } catch { return false; } } } [Serializable] public sealed class SweControlCommand { public string command; public string lod; public int tileX; public int tileY; public int sourceId; public float sourceLevel; public int sinkId; public float sinkLevel; public string checkpoint; public float u; public float v; public float radius; public float porosity; public string boundaryKind; public int boundaryId; public int enabled; public float waterLevelM; public float velocityUMps; public float velocityVMps; public float depthRateMps; public bool replaceAll; public bool subscribe; public SweBoundaryProfile[] boundaries; } [Serializable] public sealed class SweBoundaryProfile { public string boundaryKind; public int boundaryId; public bool enabled; public float waterLevelM; public float velocityUMps; public float velocityVMps; public float depthRateMps; public SweBoundaryProfile Clone() { return new SweBoundaryProfile { boundaryKind = boundaryKind, boundaryId = boundaryId, enabled = enabled, waterLevelM = waterLevelM, velocityUMps = velocityUMps, velocityVMps = velocityVMps, depthRateMps = depthRateMps, }; } } [Serializable] public sealed class SweBoundaryStateMessage { public string messageType; public int schemaVersion; public string lod; public int tileX; public int tileY; public SweBoundaryProfile[] boundaries; } }