Files
Master-Arbeit-Tom-Hempel/Unity-Master/Assets/Scripts/AvatarSyncComparison.cs
2025-09-21 22:42:26 +02:00

611 lines
27 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using UnityEngine;
using Newtonsoft.Json;
/// <summary>
/// Utility script to compare the old JSON system vs new UDP binary system
/// Attach this to any GameObject and check the console for size comparisons
/// </summary>
public class AvatarSyncComparison : MonoBehaviour
{
[Header("Comparison Settings")]
[SerializeField] private bool runComparisonOnStart = true;
[SerializeField] private bool showDetailedBreakdown = true;
[SerializeField] private bool saveToFile = true;
[SerializeField] private string outputFileName = "avatar_sync_comparison.txt";
[Header("Test Data - Avatar Reference")]
[SerializeField] private Transform testAvatar;
[SerializeField] private UDPAvatarBroadcaster udpBroadcaster; // Get actual settings
[SerializeField] private UDPAvatarReceiver udpReceiver; // Get actual settings
[Header("System Parameters")]
[SerializeField] private float updateRate = 30f; // Hz - configurable refresh rate
[SerializeField] private int oldSystemBoneCount = 80; // Calculated from avatar if available
[SerializeField] private int oldSystemMeshCount = 4; // Calculated from avatar if available
[SerializeField] private int oldSystemBlendShapesPerMesh = 52; // Calculated from avatar if available
[Header("Code Complexity (Lines of Code)")]
[SerializeField] private int oldSystemTotalLines = 2300; // Can be calculated by scanning files
[SerializeField] private int newSystemTotalLines = 800; // Can be calculated by scanning files
void Start()
{
if (runComparisonOnStart)
{
// Add 5 second delay to allow UDPAvatarBroadcaster to fully initialize
Invoke(nameof(DelayedStart), 5f);
}
}
void DelayedStart()
{
// Auto-calculate values from avatar if available
CalculateAvatarParameters();
RunComparison();
}
void CalculateAvatarParameters()
{
// Get UDPAvatarBroadcaster from same GameObject if not assigned
if (udpBroadcaster == null)
{
udpBroadcaster = GetComponent<UDPAvatarBroadcaster>();
}
// Get actual values from UDP broadcaster
if (udpBroadcaster != null)
{
updateRate = GetUDPBroadcasterUpdateRate();
Debug.Log($"Got actual update rate from UDPAvatarBroadcaster: {updateRate} Hz");
// Use the avatar from the broadcaster if not set
if (testAvatar == null)
{
testAvatar = GetUDPBroadcasterAvatarRoot();
}
}
if (testAvatar != null)
{
// Calculate actual bone count from avatar
SkinnedMeshRenderer[] meshRenderers = testAvatar.GetComponentsInChildren<SkinnedMeshRenderer>();
HashSet<Transform> uniqueBones = new HashSet<Transform>();
foreach (SkinnedMeshRenderer smr in meshRenderers)
{
if (smr.bones != null)
{
foreach (Transform bone in smr.bones)
{
if (bone != null)
{
uniqueBones.Add(bone);
}
}
}
}
if (uniqueBones.Count > 0)
{
oldSystemBoneCount = uniqueBones.Count;
}
// Calculate actual mesh and blend shape counts
if (meshRenderers.Length > 0)
{
oldSystemMeshCount = meshRenderers.Length;
// Calculate average blend shapes per mesh
int totalBlendShapes = 0;
int meshesWithBlendShapes = 0;
foreach (var mesh in meshRenderers)
{
if (mesh.sharedMesh != null && mesh.sharedMesh.blendShapeCount > 0)
{
totalBlendShapes += mesh.sharedMesh.blendShapeCount;
meshesWithBlendShapes++;
}
}
if (meshesWithBlendShapes > 0)
{
oldSystemBlendShapesPerMesh = totalBlendShapes / meshesWithBlendShapes;
}
}
}
Debug.Log($"Calculated Parameters: {oldSystemBoneCount} bones, {oldSystemMeshCount} meshes, {oldSystemBlendShapesPerMesh} blend shapes/mesh, {updateRate} Hz");
}
float GetUDPBroadcasterUpdateRate()
{
if (udpBroadcaster == null) return updateRate;
// Use reflection to get the private updateRate field
var field = typeof(UDPAvatarBroadcaster).GetField("updateRate",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (field != null)
{
return (float)field.GetValue(udpBroadcaster);
}
return updateRate; // Fallback to current value
}
Transform GetUDPBroadcasterAvatarRoot()
{
if (udpBroadcaster == null) return null;
// Use reflection to get the private avatarRoot field
var field = typeof(UDPAvatarBroadcaster).GetField("avatarRoot",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (field != null)
{
return (Transform)field.GetValue(udpBroadcaster);
}
return null;
}
bool GetUDPBroadcasterFullDataMode()
{
if (udpBroadcaster == null) return false;
// Use reflection to get the private fullDataMode field
var field = typeof(UDPAvatarBroadcaster).GetField("fullDataMode",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (field != null)
{
return (bool)field.GetValue(udpBroadcaster);
}
return false;
}
int GetUDPBroadcasterCurrentBoneCount()
{
if (udpBroadcaster == null) return 20;
// Use reflection to get the private currentBoneCount field
var field = typeof(UDPAvatarBroadcaster).GetField("currentBoneCount",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (field != null)
{
return (int)field.GetValue(udpBroadcaster);
}
return 20; // Default optimized mode
}
int GetUDPBroadcasterCurrentBlendShapeCount()
{
if (udpBroadcaster == null) return 10;
// Use reflection to get the private currentBlendShapeCount field
var field = typeof(UDPAvatarBroadcaster).GetField("currentBlendShapeCount",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (field != null)
{
return (int)field.GetValue(udpBroadcaster);
}
return 10; // Default optimized mode
}
[ContextMenu("Run Size Comparison")]
public void RunComparison()
{
Debug.Log("=== Avatar Sync System Comparison ===");
// Simulate old JSON system
var jsonSize = CalculateJSONSystemSize();
// Simulate new UDP binary system
var binarySize = CalculateUDPBinarySize();
// Calculate efficiency gains
float compressionRatio = (float)jsonSize / binarySize;
float bandwidthReduction = (1f - (float)binarySize / jsonSize) * 100f;
Debug.Log($"\n📊 SIZE COMPARISON:");
Debug.Log($"Old JSON System: {jsonSize:N0} bytes ({jsonSize / 1024f:F1} KB)");
Debug.Log($"New UDP Binary: {binarySize:N0} bytes ({binarySize / 1024f:F1} KB)");
Debug.Log($"Compression Ratio: {compressionRatio:F1}x smaller");
Debug.Log($"Bandwidth Reduction: {bandwidthReduction:F1}%");
if (showDetailedBreakdown)
{
ShowDetailedBreakdown();
}
ShowNetworkImpact(jsonSize, binarySize);
ShowSystemComplexity();
if (saveToFile)
{
SaveComparisonToFile(jsonSize, binarySize);
}
}
int CalculateJSONSystemSize()
{
// Recreate the old AvatarSyncData structure using calculated values
var oldSystemData = new
{
rootTransform = new
{
worldPosition = new { x = 0f, y = 0f, z = 0f },
worldRotation = new { x = 0f, y = 0f, z = 0f, w = 1f },
localScale = new { x = 1f, y = 1f, z = 1f }
},
bones = GenerateOldSystemBones(oldSystemBoneCount),
blendShapes = GenerateOldSystemBlendShapes(oldSystemMeshCount, oldSystemBlendShapesPerMesh),
timestamp = Time.time
};
string json = JsonConvert.SerializeObject(oldSystemData, Formatting.Indented);
int jsonBytes = Encoding.UTF8.GetByteCount(json);
Debug.Log($"📄 Old System Structure: {oldSystemBoneCount} bones, {oldSystemMeshCount} meshes × {oldSystemBlendShapesPerMesh} blend shapes = {oldSystemMeshCount * oldSystemBlendShapesPerMesh} total blend shapes");
Debug.Log($"📄 Sample JSON (first 200 chars):\n{json.Substring(0, Math.Min(200, json.Length))}...");
return jsonBytes;
}
int CalculateUDPBinarySize()
{
// Get actual bone and blend shape counts from UDP system
int actualBoneCount = GetNewSystemBoneCount();
int actualBlendShapeCount = GetNewSystemBlendShapeCount();
// Calculate new UDP binary system size
int headerSize = 1 + 4 + 4 + 1; // playerID + sequenceNumber + timestamp + isFullDataMode
int rootTransformSize = (3 + 4 + 3) * 4; // 3 Vector3s + 1 Quaternion * 4 bytes per float
int bonesSize = 4 + (actualBoneCount * (3 + 4) * 4); // bone count + bones * (position + rotation) * 4 bytes per float
int blendShapesSize = 4 + (actualBlendShapeCount * 4); // blend shape count + weights * 4 bytes per float
int totalSize = headerSize + rootTransformSize + bonesSize + blendShapesSize;
Debug.Log($"🔢 New UDP Binary Structure:");
Debug.Log($" Header: {headerSize} bytes");
Debug.Log($" Root Transform: {rootTransformSize} bytes");
Debug.Log($" Bones ({actualBoneCount}): {bonesSize} bytes");
Debug.Log($" Blend Shapes ({actualBlendShapeCount}): {blendShapesSize} bytes");
Debug.Log($" Total: {totalSize} bytes");
return totalSize;
}
int GetNewSystemBoneCount()
{
// Get actual bone count from UDP broadcaster
if (udpBroadcaster != null)
{
int actualBoneCount = GetUDPBroadcasterCurrentBoneCount();
bool fullDataMode = GetUDPBroadcasterFullDataMode();
if (showDetailedBreakdown)
{
string mode = fullDataMode ? "FULL DATA" : "OPTIMIZED";
Debug.Log($"UDP Broadcaster is in {mode} mode with {actualBoneCount} bones");
}
return actualBoneCount;
}
// Default to optimized mode bone count
return 20;
}
int GetNewSystemBlendShapeCount()
{
// Get actual blend shape count from UDP broadcaster
if (udpBroadcaster != null)
{
int actualBlendShapeCount = GetUDPBroadcasterCurrentBlendShapeCount();
bool fullDataMode = GetUDPBroadcasterFullDataMode();
if (showDetailedBreakdown)
{
string mode = fullDataMode ? "FULL DATA" : "OPTIMIZED";
Debug.Log($"UDP Broadcaster is in {mode} mode with {actualBlendShapeCount} blend shapes");
}
return actualBlendShapeCount;
}
// Default to optimized mode blend shape count
return 10;
}
object[] GenerateOldSystemBones(int count)
{
object[] bones = new object[count];
string[] boneNames = {
"Hips", "Spine", "Spine1", "Spine2", "Neck", "Head", "HeadTop_End",
"LeftEye", "RightEye", "LeftShoulder", "LeftArm", "LeftArmTwist",
"LeftForeArm", "LeftForeArmTwist", "LeftHand", "LeftHandThumb1",
"LeftHandThumb2", "LeftHandThumb3", "LeftHandThumb4", "LeftHandIndex0",
"LeftHandIndex1", "LeftHandIndex2", "LeftHandIndex3", "LeftHandIndex4",
"LeftHandMiddle0", "LeftHandMiddle1", "LeftHandMiddle2", "LeftHandMiddle3",
"LeftHandMiddle4", "LeftHandRing0", "LeftHandRing1", "LeftHandRing2",
"LeftHandRing3", "LeftHandRing4", "LeftHandPinky0", "LeftHandPinky1",
"LeftHandPinky2", "LeftHandPinky3", "LeftHandPinky4", "LeftPalm",
"RightShoulder", "RightArm", "RightArmTwist", "RightForeArm",
"RightForeArmTwist", "RightHand", "RightHandThumb1", "RightHandThumb2",
"RightHandThumb3", "RightHandThumb4", "RightHandIndex0", "RightHandIndex1",
"RightHandIndex2", "RightHandIndex3", "RightHandIndex4", "RightHandMiddle0",
"RightHandMiddle1", "RightHandMiddle2", "RightHandMiddle3", "RightHandMiddle4",
"RightHandRing0", "RightHandRing1", "RightHandRing2", "RightHandRing3",
"RightHandRing4", "RightHandPinky0", "RightHandPinky1", "RightHandPinky2",
"RightHandPinky3", "RightHandPinky4", "RightPalm", "LeftUpLeg", "LeftLeg",
"LeftFoot", "LeftToeBase", "LeftToe_End", "RightUpLeg", "RightLeg",
"RightFoot", "RightToeBase", "RightToe_End"
};
for (int i = 0; i < count; i++)
{
string boneName = i < boneNames.Length ? boneNames[i] : $"Bone_{i}";
bones[i] = new
{
boneName = boneName,
position = new { x = UnityEngine.Random.Range(-1f, 1f), y = UnityEngine.Random.Range(-1f, 1f), z = UnityEngine.Random.Range(-1f, 1f) },
rotation = new { x = UnityEngine.Random.Range(-1f, 1f), y = UnityEngine.Random.Range(-1f, 1f), z = UnityEngine.Random.Range(-1f, 1f), w = UnityEngine.Random.Range(-1f, 1f) },
scale = new { x = 1f, y = 1f, z = 1f }
};
}
return bones;
}
object[] GenerateOldSystemBlendShapes(int meshCount, int weightsPerMesh)
{
object[] blendShapes = new object[meshCount];
string[] meshNames = { "Renderer_Head", "Renderer_EyeLeft", "Renderer_EyeRight", "Renderer_Teeth" };
for (int i = 0; i < meshCount; i++)
{
float[] weights = new float[weightsPerMesh];
for (int j = 0; j < weightsPerMesh; j++)
{
weights[j] = UnityEngine.Random.Range(0f, 100f);
}
blendShapes[i] = new
{
meshName = i < meshNames.Length ? meshNames[i] : $"Mesh_{i}",
weights = weights
};
}
return blendShapes;
}
void ShowDetailedBreakdown()
{
int newSystemBones = GetNewSystemBoneCount();
int newSystemBlendShapes = GetNewSystemBlendShapeCount();
int totalOldBlendShapes = oldSystemMeshCount * oldSystemBlendShapesPerMesh;
bool fullDataMode = GetUDPBroadcasterFullDataMode();
string currentMode = fullDataMode ? "FULL DATA" : "OPTIMIZED";
Debug.Log($"\n📋 DETAILED BREAKDOWN:");
Debug.Log($"\nCurrent UDP System Configuration:");
Debug.Log($" • Mode: {currentMode}");
Debug.Log($" • Update Rate: {updateRate} Hz");
Debug.Log($" • Bones: {newSystemBones}");
Debug.Log($" • Blend Shapes: {newSystemBlendShapes}");
Debug.Log($"\nOld JSON System Issues:");
Debug.Log($" • {oldSystemBoneCount} bones with full transform data");
Debug.Log($" • {oldSystemMeshCount} meshes × {oldSystemBlendShapesPerMesh} blend shapes = {totalOldBlendShapes} facial weights");
Debug.Log($" • Verbose JSON with field names repeated");
Debug.Log($" • UTF-8 string encoding overhead");
Debug.Log($" • HTTP headers and protocol overhead");
Debug.Log($" • Requires Python server infrastructure");
Debug.Log($"\nNew UDP Binary Advantages:");
if (fullDataMode)
{
Debug.Log($" • {newSystemBones} bones (FULL avatar data)");
Debug.Log($" • {newSystemBlendShapes} facial blend shapes (ALL expressions)");
}
else
{
Debug.Log($" • Only {newSystemBones} priority bones (most important)");
Debug.Log($" • {newSystemBlendShapes} key facial blend shapes only");
}
Debug.Log($" • Compact binary format, no field names");
Debug.Log($" • Variable-size packets with length prefixes");
Debug.Log($" • Direct UDP broadcast, no server needed");
Debug.Log($" • Predictable network usage");
float boneReduction = (1f - (float)newSystemBones / oldSystemBoneCount) * 100f;
float blendShapeReduction = (1f - (float)newSystemBlendShapes / totalOldBlendShapes) * 100f;
Debug.Log($"\nData Reduction vs Old System:");
Debug.Log($" • Bones: {boneReduction:F1}% reduction ({oldSystemBoneCount} → {newSystemBones})");
Debug.Log($" • Blend Shapes: {blendShapeReduction:F1}% reduction ({totalOldBlendShapes} → {newSystemBlendShapes})");
}
void ShowNetworkImpact(int jsonSize, int binarySize)
{
Debug.Log($"\n🌐 NETWORK IMPACT (at {updateRate} Hz):");
float jsonBandwidthKBps = (jsonSize * updateRate) / 1024f;
float binaryBandwidthKBps = (binarySize * updateRate) / 1024f;
Debug.Log($"Old System: {jsonBandwidthKBps:F1} KB/s per player");
Debug.Log($"New System: {binaryBandwidthKBps:F1} KB/s per player");
// Calculate for 3 player setup
int playerCount = 3;
Debug.Log($"\n3 Player Setup:");
Debug.Log($"Old System: {jsonBandwidthKBps * playerCount:F1} KB/s total");
Debug.Log($"New System: {binaryBandwidthKBps * playerCount:F1} KB/s total");
Debug.Log($"\nData usage per hour (single player):");
float oldMBPerHour = (jsonBandwidthKBps * 3600) / 1024;
float newMBPerHour = (binaryBandwidthKBps * 3600) / 1024;
Debug.Log($"Old System: {oldMBPerHour:F1} MB/hour");
Debug.Log($"New System: {newMBPerHour:F1} MB/hour");
Debug.Log($"Savings: {oldMBPerHour - newMBPerHour:F1} MB/hour ({((oldMBPerHour - newMBPerHour) / oldMBPerHour) * 100:F1}% reduction)");
}
void ShowSystemComplexity()
{
float codeReduction = (1f - (float)newSystemTotalLines / oldSystemTotalLines) * 100f;
Debug.Log($"\n⚙ SYSTEM COMPLEXITY:");
Debug.Log($"\nOld System Components:");
Debug.Log($" • avatar_sync_server.py");
Debug.Log($" • AvatarSyncClient.cs");
Debug.Log($" • AvatarDataUploader.cs");
Debug.Log($" • AvatarDataDownloader.cs");
Debug.Log($" • AvatarDataWriter.cs");
Debug.Log($" • AvatarDataReader.cs");
Debug.Log($" • Multiple serialization classes");
Debug.Log($" • HTTP client/server infrastructure");
Debug.Log($" Total: ~{oldSystemTotalLines:N0} lines of code");
Debug.Log($"\nNew System Components:");
Debug.Log($" • UDPAvatarBroadcaster.cs");
Debug.Log($" • UDPAvatarReceiver.cs");
Debug.Log($" • Simple binary serialization");
Debug.Log($" • No server infrastructure needed");
Debug.Log($" Total: ~{newSystemTotalLines:N0} lines of code");
Debug.Log($"\n✅ New system is {codeReduction:F1}% less code and much simpler!");
Debug.Log($"Code reduction: {oldSystemTotalLines:N0} → {newSystemTotalLines:N0} lines ({oldSystemTotalLines - newSystemTotalLines:N0} lines removed)");
}
void SaveComparisonToFile(int jsonSize, int binarySize)
{
try
{
// Calculate all metrics using dynamic values
float compressionRatio = (float)jsonSize / binarySize;
float bandwidthReduction = (1f - (float)binarySize / jsonSize) * 100f;
float jsonBandwidthKBps = (jsonSize * updateRate) / 1024f;
float binaryBandwidthKBps = (binarySize * updateRate) / 1024f;
// Get calculated counts
int newSystemBones = GetNewSystemBoneCount();
int newSystemBlendShapes = GetNewSystemBlendShapeCount();
int totalOldBlendShapes = oldSystemMeshCount * oldSystemBlendShapesPerMesh;
bool fullDataMode = GetUDPBroadcasterFullDataMode();
string currentMode = fullDataMode ? "FULL DATA" : "OPTIMIZED";
// Create file content with just the data
StringBuilder fileContent = new StringBuilder();
fileContent.AppendLine("Avatar Sync System Comparison Results");
fileContent.AppendLine($"Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
fileContent.AppendLine();
// Size comparison
fileContent.AppendLine("PACKET SIZE COMPARISON:");
fileContent.AppendLine($"Old JSON System: {jsonSize:N0} bytes ({jsonSize / 1024f:F2} KB)");
fileContent.AppendLine($"New UDP Binary: {binarySize:N0} bytes ({binarySize / 1024f:F2} KB)");
fileContent.AppendLine($"Compression Ratio: {compressionRatio:F1}x smaller");
fileContent.AppendLine($"Size Reduction: {bandwidthReduction:F1}%");
fileContent.AppendLine();
// Binary structure breakdown using calculated values
int headerSize = 1 + 4 + 4 + 1; // playerID + sequenceNumber + timestamp + isFullDataMode
int rootTransformSize = (3 + 4 + 3) * 4;
int bonesSize = 4 + (newSystemBones * (3 + 4) * 4);
int blendShapesSize = 4 + (newSystemBlendShapes * 4);
fileContent.AppendLine("UDP BINARY STRUCTURE:");
fileContent.AppendLine($"Header: {headerSize} bytes");
fileContent.AppendLine($"Root Transform: {rootTransformSize} bytes");
fileContent.AppendLine($"Bones ({newSystemBones}): {bonesSize} bytes");
fileContent.AppendLine($"Blend Shapes ({newSystemBlendShapes}): {blendShapesSize} bytes");
fileContent.AppendLine($"Total: {binarySize} bytes");
fileContent.AppendLine();
// Bandwidth impact at configured rate
fileContent.AppendLine($"BANDWIDTH USAGE ({updateRate} Hz):");
fileContent.AppendLine($"Old System: {jsonBandwidthKBps:F1} KB/s per player");
fileContent.AppendLine($"New System: {binaryBandwidthKBps:F1} KB/s per player");
fileContent.AppendLine();
// System details
fileContent.AppendLine("SYSTEM DETAILS:");
fileContent.AppendLine($"Update Rate: {updateRate} Hz");
fileContent.AppendLine($"UDP Mode: {currentMode}");
fileContent.AppendLine($"Old System: {oldSystemBoneCount} bones, {totalOldBlendShapes} blend shapes ({oldSystemMeshCount} meshes × {oldSystemBlendShapesPerMesh})");
fileContent.AppendLine($"New System: {newSystemBones} bones, {newSystemBlendShapes} blend shapes");
fileContent.AppendLine();
// 3 Player setup bandwidth
int players = 3;
float oldTotal = jsonBandwidthKBps * players;
float newTotal = binaryBandwidthKBps * players;
fileContent.AppendLine("3 PLAYER SETUP BANDWIDTH:");
fileContent.AppendLine($"Old System: {oldTotal:F1} KB/s total");
fileContent.AppendLine($"New System: {newTotal:F1} KB/s total");
fileContent.AppendLine($"Bandwidth Savings: {oldTotal - newTotal:F1} KB/s ({((oldTotal - newTotal) / oldTotal) * 100:F1}% reduction)");
fileContent.AppendLine();
// Data usage per hour
float oldMBPerHour = (jsonBandwidthKBps * 3600) / 1024;
float newMBPerHour = (binaryBandwidthKBps * 3600) / 1024;
fileContent.AppendLine("DATA USAGE PER HOUR (single player):");
fileContent.AppendLine($"Old System: {oldMBPerHour:F1} MB/hour");
fileContent.AppendLine($"New System: {newMBPerHour:F1} MB/hour");
fileContent.AppendLine($"Savings: {oldMBPerHour - newMBPerHour:F1} MB/hour ({((oldMBPerHour - newMBPerHour) / oldMBPerHour) * 100:F1}% reduction)");
fileContent.AppendLine();
// Code complexity
float codeReduction = (1f - (float)newSystemTotalLines / oldSystemTotalLines) * 100f;
fileContent.AppendLine("CODE COMPLEXITY:");
fileContent.AppendLine($"Old System: ~{oldSystemTotalLines:N0} lines (6 components + Python server)");
fileContent.AppendLine($"New System: ~{newSystemTotalLines:N0} lines (2 components, no server)");
fileContent.AppendLine($"Code Reduction: {codeReduction:F1}% ({oldSystemTotalLines - newSystemTotalLines:N0} lines removed)");
fileContent.AppendLine();
// Performance characteristics
fileContent.AppendLine("PERFORMANCE CHARACTERISTICS:");
fileContent.AppendLine("Old System: HTTP request/response, JSON parsing, server dependency");
fileContent.AppendLine("New System: UDP broadcast, binary format, peer-to-peer");
fileContent.AppendLine("Latency: Local network UDP (~1-10ms vs HTTP ~10-50ms)");
fileContent.AppendLine("Infrastructure: None required (vs Python server)");
// Write to file
string filePath = Path.Combine(Application.dataPath, outputFileName);
File.WriteAllText(filePath, fileContent.ToString());
Debug.Log($"Comparison data saved to: {filePath}");
}
catch (Exception e)
{
Debug.LogError($"Failed to save comparison to file: {e.Message}");
}
}
void OnGUI()
{
if (GUI.Button(new Rect(10, Screen.height - 40, 200, 30), "Run Comparison"))
{
RunComparison();
}
if (GUI.Button(new Rect(220, Screen.height - 40, 150, 30), "Save to File"))
{
var jsonSize = CalculateJSONSystemSize();
var binarySize = CalculateUDPBinarySize();
SaveComparisonToFile(jsonSize, binarySize);
}
}
}