#if UNITY_EDITOR using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; namespace GameCult.Epiphany.Unity { public static class EpiphanyEditorBridge { private const int DefaultMaxObjects = 600; private const int DefaultMaxProperties = 100; private const int DefaultMaxDependencies = 2000; private const string ArtifactDirArg = "-epiphanyArtifactDir"; private const string OperationArg = "-epiphanyOperation"; private const string SceneArg = "-epiphanyScene"; private const string AssetArg = "-epiphanyAsset"; private const string GuidArg = "-epiphanyGuid"; private const string MaxObjectsArg = "-epiphanyMaxObjects"; private const string MaxPropertiesArg = "-epiphanyMaxProperties"; private const string MaxDependenciesArg = "-epiphanyMaxDependencies"; private static string currentArtifactDir; [MenuItem("Epiphany/Inspect Project")] public static void InspectProject() { RunCommand("inspect-project", CaptureProjectFacts); } [MenuItem("Epiphany/Check Compilation")] public static void CheckCompilation() { RunCommand("check-compilation", CaptureCompilationFacts); } [MenuItem("Epiphany/Capture Scene Facts")] public static void CaptureSceneFacts() { RunCommand("scene-facts", delegate { return CaptureSceneFactsPayload(GetScenePathArg()); }); } [MenuItem("Epiphany/Capture Prefab Facts")] public static void CapturePrefabFacts() { RunCommand("prefab-facts", delegate { return CapturePrefabFactsPayload(GetAssetPathArg()); }); } [MenuItem("Epiphany/Capture Serialized Object Facts")] public static void CaptureSerializedObjectFacts() { RunCommand("serialized-object", delegate { return CaptureSerializedObjectFactsPayload(GetAssetPathArg()); }); } [MenuItem("Epiphany/Reference Search")] public static void ReferenceSearch() { RunCommand("reference-search", CaptureReferenceSearchPayload); } public static void RunProbe() { string operation = GetArg(OperationArg, "inspect-project"); switch (operation) { case "inspect-project": InspectProject(); break; case "check-compilation": CheckCompilation(); break; case "scene-facts": CaptureSceneFacts(); break; case "prefab-facts": CapturePrefabFacts(); break; case "serialized-object": CaptureSerializedObjectFacts(); break; case "reference-search": ReferenceSearch(); break; default: RunCommand(operation, delegate { Dictionary payload = BaseResult("unityProbeResult", operation); payload["status"] = "blocked"; payload["evidenceSummary"] = "Unknown Epiphany Unity probe operation."; payload["knownOperations"] = new List { "inspect-project", "check-compilation", "scene-facts", "prefab-facts", "serialized-object", "reference-search" }; return payload; }); EditorApplication.Exit(2); break; } } private static void RunCommand(string operation, Func> buildPayload) { string artifactDir = ResolveArtifactDir(operation); currentArtifactDir = artifactDir; Directory.CreateDirectory(artifactDir); Dictionary payload; try { payload = buildPayload(); if (!payload.ContainsKey("operation")) { payload["operation"] = operation; } if (!payload.ContainsKey("status")) { payload["status"] = "passed"; } } catch (Exception error) { payload = BaseResult("unityProbeResult", operation); payload["status"] = "failed"; payload["error"] = error.ToString(); payload["evidenceSummary"] = "The Epiphany Unity editor bridge failed while gathering editor facts."; WriteArtifacts(artifactDir, operation, payload); Debug.LogError(error); EditorApplication.Exit(1); return; } WriteArtifacts(artifactDir, operation, payload); if (StringEquals(payload, "status", "failed")) { EditorApplication.Exit(1); } } private static Dictionary CaptureProjectFacts() { Dictionary payload = BaseResult("unityProjectInspection", "inspect-project"); payload["status"] = "passed"; payload["unityVersion"] = Application.unityVersion; payload["projectPath"] = ProjectRoot(); payload["dataPath"] = Application.dataPath; payload["isBatchMode"] = Application.isBatchMode; payload["isCompiling"] = EditorApplication.isCompiling; payload["isUpdating"] = EditorApplication.isUpdating; payload["activeScene"] = SceneSummary(SceneManager.GetActiveScene()); payload["buildScenes"] = BuildScenes(); payload["renderPipeline"] = RenderPipelineSummary(); payload["assetDatabase"] = AssetDatabaseSummary(); payload["evidenceSummary"] = "Unity editor project facts were captured from inside the pinned editor."; WriteNamedJson("project-facts.json", payload); return payload; } private static Dictionary CaptureCompilationFacts() { Dictionary payload = BaseResult("unityCompilationProbe", "check-compilation"); bool compiling = EditorApplication.isCompiling; bool updating = EditorApplication.isUpdating; payload["status"] = compiling || updating ? "pending" : "passed"; payload["isCompiling"] = compiling; payload["isUpdating"] = updating; payload["unityVersion"] = Application.unityVersion; payload["loadedAssemblies"] = LoadedAssemblySummary(); payload["evidenceSummary"] = compiling || updating ? "The editor bridge ran, but Unity reports compilation or asset update still in progress." : "The editor bridge executed inside Unity, which means editor scripts compiled far enough for probes to run."; WriteNamedJson("compilation.json", payload); return payload; } private static Dictionary CaptureSceneFactsPayload(string scenePath) { if (string.IsNullOrEmpty(scenePath)) { scenePath = DefaultScenePath(); } if (string.IsNullOrEmpty(scenePath)) { throw new InvalidOperationException("No scene path was supplied and no default build scene exists."); } Scene scene = SceneManager.GetActiveScene(); if (!string.Equals(scene.path, scenePath, StringComparison.OrdinalIgnoreCase)) { scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single); } Dictionary payload = BaseResult("unitySceneFacts", "scene-facts"); payload["status"] = "passed"; payload["scene"] = SceneSummary(scene); payload["rootCount"] = scene.rootCount; payload["isDirty"] = scene.isDirty; int maxObjects = GetIntArg(MaxObjectsArg, DefaultMaxObjects); List objects = new List(); int totalObjects = 0; int missingScripts = 0; foreach (GameObject root in scene.GetRootGameObjects()) { WalkGameObject(root, objects, ref totalObjects, ref missingScripts, maxObjects); } payload["totalGameObjectCount"] = totalObjects; payload["capturedGameObjectCount"] = objects.Count; payload["truncated"] = totalObjects > objects.Count; payload["missingScriptCount"] = missingScripts; payload["gameObjects"] = objects; payload["evidenceSummary"] = "Scene hierarchy, components, prefab links, and serialized fields were captured inside Unity."; WriteNamedJson("scene-facts.json", payload); return payload; } private static Dictionary CapturePrefabFactsPayload(string assetPath) { if (string.IsNullOrEmpty(assetPath)) { throw new InvalidOperationException("Prefab facts require -epiphanyAsset Assets/.../Prefab.prefab."); } GameObject root = PrefabUtility.LoadPrefabContents(assetPath); try { Dictionary payload = BaseResult("unityPrefabFacts", "prefab-facts"); payload["status"] = "passed"; payload["assetPath"] = assetPath; payload["guid"] = AssetDatabase.AssetPathToGUID(assetPath); payload["prefabAssetType"] = PrefabUtility.GetPrefabAssetType(root).ToString(); payload["root"] = ObjectReferenceSummary(root); int maxObjects = GetIntArg(MaxObjectsArg, DefaultMaxObjects); List objects = new List(); int totalObjects = 0; int missingScripts = 0; WalkGameObject(root, objects, ref totalObjects, ref missingScripts, maxObjects); payload["totalGameObjectCount"] = totalObjects; payload["capturedGameObjectCount"] = objects.Count; payload["truncated"] = totalObjects > objects.Count; payload["missingScriptCount"] = missingScripts; payload["gameObjects"] = objects; payload["evidenceSummary"] = "Prefab hierarchy, nested instances, overrides, components, and serialized fields were captured inside Unity."; WriteNamedJson("prefab-facts.json", payload); return payload; } finally { PrefabUtility.UnloadPrefabContents(root); } } private static Dictionary CaptureSerializedObjectFactsPayload(string assetPath) { if (string.IsNullOrEmpty(assetPath)) { throw new InvalidOperationException("Serialized object facts require -epiphanyAsset Assets/... path."); } UnityEngine.Object[] assets = AssetDatabase.LoadAllAssetsAtPath(assetPath); Dictionary payload = BaseResult("unitySerializedObjectFacts", "serialized-object"); payload["status"] = "passed"; payload["assetPath"] = assetPath; payload["guid"] = AssetDatabase.AssetPathToGUID(assetPath); payload["assetCount"] = assets == null ? 0 : assets.Length; List targets = new List(); if (assets != null) { foreach (UnityEngine.Object asset in assets) { if (asset == null) { continue; } targets.Add(SerializedObjectSummary(asset)); } } payload["targets"] = targets; payload["evidenceSummary"] = "Serialized Unity object properties were captured through SerializedObject inside the editor."; WriteNamedJson("serialized-object-facts.json", payload); return payload; } private static Dictionary CaptureReferenceSearchPayload() { string assetPath = GetArg(AssetArg, null); string guid = GetArg(GuidArg, null); if (string.IsNullOrEmpty(guid) && !string.IsNullOrEmpty(assetPath)) { guid = AssetDatabase.AssetPathToGUID(assetPath); } if (string.IsNullOrEmpty(guid)) { throw new InvalidOperationException("Reference search requires -epiphanyGuid or -epiphanyAsset."); } string targetPath = AssetDatabase.GUIDToAssetPath(guid); int maxDependencies = GetIntArg(MaxDependenciesArg, DefaultMaxDependencies); Dictionary payload = BaseResult("unityReferenceSearch", "reference-search"); payload["status"] = "passed"; payload["targetGuid"] = guid; payload["targetPath"] = targetPath; List matches = new List(); int scanned = 0; string[] paths = AssetDatabase.GetAllAssetPaths(); foreach (string path in paths) { if (!path.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase)) { continue; } scanned++; string[] dependencies = AssetDatabase.GetDependencies(path, true); bool found = false; foreach (string dependency in dependencies) { if (string.Equals(dependency, targetPath, StringComparison.OrdinalIgnoreCase)) { found = true; break; } } if (!found) { continue; } Dictionary match = new Dictionary(); match["path"] = path; match["guid"] = AssetDatabase.AssetPathToGUID(path); match["type"] = AssetTypeName(path); matches.Add(match); if (matches.Count >= maxDependencies) { break; } } payload["scannedAssetCount"] = scanned; payload["matchCount"] = matches.Count; payload["truncated"] = matches.Count >= maxDependencies; payload["matches"] = matches; payload["evidenceSummary"] = "Unity asset dependencies were searched through AssetDatabase."; WriteNamedJson("reference-search.json", payload); return payload; } private static void WalkGameObject(GameObject gameObject, List output, ref int totalObjects, ref int missingScripts, int maxObjects) { totalObjects++; if (output.Count < maxObjects) { output.Add(GameObjectSummary(gameObject, ref missingScripts)); } else { Component[] missingCountOnly = gameObject.GetComponents(); foreach (Component component in missingCountOnly) { if (component == null) { missingScripts++; } } } Transform transform = gameObject.transform; for (int i = 0; i < transform.childCount; i++) { WalkGameObject(transform.GetChild(i).gameObject, output, ref totalObjects, ref missingScripts, maxObjects); } } private static Dictionary GameObjectSummary(GameObject gameObject, ref int missingScripts) { Dictionary summary = new Dictionary(); summary["name"] = gameObject.name; summary["path"] = GameObjectPath(gameObject); summary["activeSelf"] = gameObject.activeSelf; summary["activeInHierarchy"] = gameObject.activeInHierarchy; summary["tag"] = gameObject.tag; summary["layer"] = gameObject.layer; summary["scenePath"] = gameObject.scene.path; summary["globalObjectId"] = GlobalId(gameObject); summary["prefab"] = PrefabSummary(gameObject); List components = new List(); Component[] componentArray = gameObject.GetComponents(); foreach (Component component in componentArray) { if (component == null) { missingScripts++; Dictionary missing = new Dictionary(); missing["missing"] = true; components.Add(missing); continue; } components.Add(ComponentSummary(component)); } summary["components"] = components; return summary; } private static Dictionary ComponentSummary(Component component) { Dictionary summary = ObjectReferenceSummary(component); Behaviour behaviour = component as Behaviour; if (behaviour != null) { summary["enabled"] = behaviour.enabled; } Renderer renderer = component as Renderer; if (renderer != null) { List materials = new List(); foreach (Material material in renderer.sharedMaterials) { materials.Add(ObjectReferenceSummary(material)); } summary["sharedMaterials"] = materials; } summary["serializedProperties"] = SerializedProperties(component, GetIntArg(MaxPropertiesArg, DefaultMaxProperties)); return summary; } private static Dictionary SerializedObjectSummary(UnityEngine.Object target) { Dictionary summary = ObjectReferenceSummary(target); summary["serializedProperties"] = SerializedProperties(target, GetIntArg(MaxPropertiesArg, DefaultMaxProperties)); GameObject gameObject = target as GameObject; if (gameObject != null) { int missing = 0; summary["gameObject"] = GameObjectSummary(gameObject, ref missing); summary["missingScriptCount"] = missing; } return summary; } private static List SerializedProperties(UnityEngine.Object target, int maxProperties) { List properties = new List(); if (target == null) { return properties; } try { SerializedObject serializedObject = new SerializedObject(target); SerializedProperty iterator = serializedObject.GetIterator(); bool enterChildren = true; int count = 0; while (iterator.NextVisible(enterChildren)) { enterChildren = false; properties.Add(SerializedPropertySummary(iterator)); count++; if (count >= maxProperties) { Dictionary truncated = new Dictionary(); truncated["propertyPath"] = ""; truncated["reason"] = "max property limit reached"; truncated["maxProperties"] = maxProperties; properties.Add(truncated); break; } } } catch (Exception error) { Dictionary failed = new Dictionary(); failed["propertyPath"] = ""; failed["error"] = error.Message; properties.Add(failed); } return properties; } private static Dictionary SerializedPropertySummary(SerializedProperty property) { Dictionary summary = new Dictionary(); summary["propertyPath"] = property.propertyPath; summary["displayName"] = property.displayName; summary["type"] = property.propertyType.ToString(); summary["isArray"] = property.isArray; summary["hasVisibleChildren"] = property.hasVisibleChildren; summary["value"] = SerializedPropertyValue(property); return summary; } private static object SerializedPropertyValue(SerializedProperty property) { try { switch (property.propertyType) { case SerializedPropertyType.Integer: return property.longValue; case SerializedPropertyType.Boolean: return property.boolValue; case SerializedPropertyType.Float: return property.doubleValue; case SerializedPropertyType.String: return property.stringValue; case SerializedPropertyType.Color: return ColorSummary(property.colorValue); case SerializedPropertyType.ObjectReference: return ObjectReferenceSummary(property.objectReferenceValue); case SerializedPropertyType.LayerMask: return property.intValue; case SerializedPropertyType.Enum: return property.enumDisplayNames != null && property.enumValueIndex >= 0 && property.enumValueIndex < property.enumDisplayNames.Length ? property.enumDisplayNames[property.enumValueIndex] : property.enumValueIndex.ToString(CultureInfo.InvariantCulture); case SerializedPropertyType.Vector2: return VectorSummary(property.vector2Value); case SerializedPropertyType.Vector3: return VectorSummary(property.vector3Value); case SerializedPropertyType.Vector4: return VectorSummary(property.vector4Value); case SerializedPropertyType.Rect: return RectSummary(property.rectValue); case SerializedPropertyType.ArraySize: return property.intValue; case SerializedPropertyType.Character: return property.intValue; case SerializedPropertyType.Bounds: return BoundsSummary(property.boundsValue); case SerializedPropertyType.Quaternion: return QuaternionSummary(property.quaternionValue); default: Dictionary unsupported = new Dictionary(); unsupported["unsupported"] = true; unsupported["propertyType"] = property.propertyType.ToString(); return unsupported; } } catch (Exception error) { Dictionary failed = new Dictionary(); failed["error"] = error.Message; return failed; } } private static Dictionary PrefabSummary(GameObject gameObject) { Dictionary summary = new Dictionary(); summary["assetType"] = PrefabUtility.GetPrefabAssetType(gameObject).ToString(); summary["instanceStatus"] = PrefabUtility.GetPrefabInstanceStatus(gameObject).ToString(); UnityEngine.Object source = PrefabUtility.GetCorrespondingObjectFromSource(gameObject); summary["source"] = ObjectReferenceSummary(source); summary["nearestInstanceRoot"] = ObjectReferenceSummary(PrefabUtility.GetNearestPrefabInstanceRoot(gameObject)); return summary; } private static Dictionary ObjectReferenceSummary(UnityEngine.Object value) { Dictionary summary = new Dictionary(); if (value == null) { summary["isNull"] = true; return summary; } summary["isNull"] = false; summary["name"] = value.name; summary["type"] = value.GetType().FullName; summary["globalObjectId"] = GlobalId(value); string path = AssetDatabase.GetAssetPath(value); if (!string.IsNullOrEmpty(path)) { summary["assetPath"] = path; summary["guid"] = AssetDatabase.AssetPathToGUID(path); } GameObject gameObject = value as GameObject; if (gameObject != null) { summary["scenePath"] = gameObject.scene.path; summary["gameObjectPath"] = GameObjectPath(gameObject); } Component component = value as Component; if (component != null) { summary["scenePath"] = component.gameObject.scene.path; summary["gameObjectPath"] = GameObjectPath(component.gameObject); } return summary; } private static Dictionary SceneSummary(Scene scene) { Dictionary summary = new Dictionary(); summary["name"] = scene.name; summary["path"] = scene.path; summary["isLoaded"] = scene.isLoaded; summary["isDirty"] = scene.isDirty; summary["rootCount"] = scene.rootCount; summary["buildIndex"] = scene.buildIndex; return summary; } private static List BuildScenes() { List scenes = new List(); foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes) { Dictionary item = new Dictionary(); item["path"] = scene.path; item["guid"] = scene.guid.ToString(); item["enabled"] = scene.enabled; scenes.Add(item); } return scenes; } private static Dictionary RenderPipelineSummary() { Dictionary summary = new Dictionary(); UnityEngine.Rendering.RenderPipelineAsset pipeline = UnityEngine.Rendering.GraphicsSettings.defaultRenderPipeline; summary["defaultRenderPipeline"] = ObjectReferenceSummary(pipeline); return summary; } private static Dictionary AssetDatabaseSummary() { Dictionary summary = new Dictionary(); summary["globalArtifactDependencyVersion"] = AssetDatabase.GlobalArtifactDependencyVersion; summary["globalArtifactProcessedVersion"] = AssetDatabase.GlobalArtifactProcessedVersion; summary["activeRefreshImportMode"] = AssetDatabase.ActiveRefreshImportMode.ToString(); return summary; } private static List LoadedAssemblySummary() { List assemblies = new List(); foreach (System.Reflection.Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { string name = assembly.GetName().Name; if (string.IsNullOrEmpty(name)) { continue; } if (!name.StartsWith("Assembly-CSharp", StringComparison.OrdinalIgnoreCase) && !name.StartsWith("Aetheria", StringComparison.OrdinalIgnoreCase) && !name.StartsWith("Cult", StringComparison.OrdinalIgnoreCase)) { continue; } Dictionary item = new Dictionary(); item["name"] = name; item["fullName"] = assembly.FullName; assemblies.Add(item); } return assemblies; } private static string DefaultScenePath() { Scene active = SceneManager.GetActiveScene(); if (!string.IsNullOrEmpty(active.path)) { return active.path; } foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes) { if (scene.enabled && !string.IsNullOrEmpty(scene.path)) { return scene.path; } } return null; } private static string GetScenePathArg() { return NormalizeAssetPath(GetArg(SceneArg, null)); } private static string GetAssetPathArg() { return NormalizeAssetPath(GetArg(AssetArg, null)); } private static string NormalizeAssetPath(string value) { if (string.IsNullOrEmpty(value)) { return value; } return value.Replace("\\", "/"); } private static string AssetTypeName(string path) { Type type = AssetDatabase.GetMainAssetTypeAtPath(path); return type == null ? null : type.FullName; } private static string GameObjectPath(GameObject gameObject) { if (gameObject == null) { return null; } List names = new List(); Transform current = gameObject.transform; while (current != null) { names.Add(current.name); current = current.parent; } names.Reverse(); return string.Join("/", names.ToArray()); } private static string GlobalId(UnityEngine.Object unityObject) { if (unityObject == null) { return null; } try { return GlobalObjectId.GetGlobalObjectIdSlow(unityObject).ToString(); } catch { return null; } } private static Dictionary BaseResult(string kind, string operation) { Dictionary payload = new Dictionary(); payload["kind"] = kind; payload["operation"] = operation; payload["generatedAt"] = DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture); payload["projectPath"] = ProjectRoot(); payload["unityVersion"] = Application.unityVersion; payload["isBatchMode"] = Application.isBatchMode; return payload; } private static string ProjectRoot() { return Directory.GetParent(Application.dataPath).FullName.Replace("\\", "/"); } private static string ResolveArtifactDir(string operation) { string fromArgs = GetArg(ArtifactDirArg, null); if (!string.IsNullOrEmpty(fromArgs)) { return Path.GetFullPath(fromArgs); } string root = Path.Combine(ProjectRoot(), "EpiphanyArtifacts"); string stamp = DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture); return Path.Combine(root, operation + "-" + stamp); } private static void WriteArtifacts(string artifactDir, string operation, Dictionary payload) { Directory.CreateDirectory(artifactDir); payload["artifactDir"] = artifactDir.Replace("\\", "/"); string resultPath = Path.Combine(artifactDir, "unity-probe-result.json"); File.WriteAllText(resultPath, MiniJson.Serialize(payload), Encoding.UTF8); string markdownPath = Path.Combine(artifactDir, "unity-probe-result.md"); File.WriteAllText(markdownPath, RenderMarkdown(payload), Encoding.UTF8); Debug.Log("Epiphany Unity bridge wrote " + operation + " artifacts to " + artifactDir); } private static void WriteNamedJson(string fileName, Dictionary payload) { string artifactDir = string.IsNullOrEmpty(currentArtifactDir) ? ResolveArtifactDir(payload.ContainsKey("operation") ? Convert.ToString(payload["operation"]) : "probe") : currentArtifactDir; WriteNamedJsonFile(artifactDir, fileName, payload); } private static string RenderMarkdown(Dictionary payload) { StringBuilder builder = new StringBuilder(); builder.AppendLine("# Epiphany Unity Probe Result"); builder.AppendLine(); AppendMarkdownValue(builder, "Kind", payload, "kind"); AppendMarkdownValue(builder, "Operation", payload, "operation"); AppendMarkdownValue(builder, "Status", payload, "status"); AppendMarkdownValue(builder, "Unity", payload, "unityVersion"); AppendMarkdownValue(builder, "Project", payload, "projectPath"); AppendMarkdownValue(builder, "Generated", payload, "generatedAt"); AppendMarkdownValue(builder, "Summary", payload, "evidenceSummary"); builder.AppendLine(); builder.AppendLine("Raw facts are in `unity-probe-result.json` and operation-specific JSON files when available."); return builder.ToString(); } private static void AppendMarkdownValue(StringBuilder builder, string label, Dictionary payload, string key) { object value; if (payload.TryGetValue(key, out value) && value != null) { builder.Append(label).Append(": ").AppendLine(Convert.ToString(value, CultureInfo.InvariantCulture)); } } private static void WriteNamedJsonFile(string artifactDir, string fileName, Dictionary payload) { Directory.CreateDirectory(artifactDir); File.WriteAllText(Path.Combine(artifactDir, fileName), MiniJson.Serialize(payload), Encoding.UTF8); } private static Dictionary ColorSummary(Color value) { Dictionary summary = new Dictionary(); summary["r"] = value.r; summary["g"] = value.g; summary["b"] = value.b; summary["a"] = value.a; return summary; } private static Dictionary VectorSummary(Vector2 value) { Dictionary summary = new Dictionary(); summary["x"] = value.x; summary["y"] = value.y; return summary; } private static Dictionary VectorSummary(Vector3 value) { Dictionary summary = VectorSummary((Vector2)value); summary["z"] = value.z; return summary; } private static Dictionary VectorSummary(Vector4 value) { Dictionary summary = new Dictionary(); summary["x"] = value.x; summary["y"] = value.y; summary["z"] = value.z; summary["w"] = value.w; return summary; } private static Dictionary RectSummary(Rect value) { Dictionary summary = new Dictionary(); summary["x"] = value.x; summary["y"] = value.y; summary["width"] = value.width; summary["height"] = value.height; return summary; } private static Dictionary BoundsSummary(Bounds value) { Dictionary summary = new Dictionary(); summary["center"] = VectorSummary(value.center); summary["size"] = VectorSummary(value.size); return summary; } private static Dictionary QuaternionSummary(Quaternion value) { Dictionary summary = new Dictionary(); summary["x"] = value.x; summary["y"] = value.y; summary["z"] = value.z; summary["w"] = value.w; return summary; } private static bool StringEquals(Dictionary payload, string key, string expected) { object value; return payload.TryGetValue(key, out value) && string.Equals(Convert.ToString(value), expected, StringComparison.OrdinalIgnoreCase); } private static string GetArg(string name, string fallback) { string[] args = Environment.GetCommandLineArgs(); for (int i = 0; i < args.Length - 1; i++) { if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase)) { return args[i + 1]; } } return fallback; } private static int GetIntArg(string name, int fallback) { string raw = GetArg(name, null); int value; return int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out value) ? value : fallback; } private static class MiniJson { public static string Serialize(object value) { StringBuilder builder = new StringBuilder(); WriteValue(builder, value); builder.AppendLine(); return builder.ToString(); } private static void WriteValue(StringBuilder builder, object value) { if (value == null) { builder.Append("null"); return; } string asString = value as string; if (asString != null) { WriteString(builder, asString); return; } if (value is bool) { builder.Append((bool)value ? "true" : "false"); return; } if (IsNumber(value)) { builder.Append(Convert.ToString(value, CultureInfo.InvariantCulture)); return; } IDictionary dictionary = value as IDictionary; if (dictionary != null) { WriteObject(builder, dictionary); return; } IEnumerable enumerable = value as IEnumerable; if (enumerable != null) { WriteArray(builder, enumerable); return; } WriteString(builder, Convert.ToString(value, CultureInfo.InvariantCulture)); } private static void WriteObject(StringBuilder builder, IDictionary dictionary) { builder.Append("{"); bool first = true; foreach (DictionaryEntry entry in dictionary) { if (!first) { builder.Append(","); } first = false; WriteString(builder, Convert.ToString(entry.Key, CultureInfo.InvariantCulture)); builder.Append(":"); WriteValue(builder, entry.Value); } builder.Append("}"); } private static void WriteArray(StringBuilder builder, IEnumerable enumerable) { builder.Append("["); bool first = true; foreach (object item in enumerable) { if (!first) { builder.Append(","); } first = false; WriteValue(builder, item); } builder.Append("]"); } private static void WriteString(StringBuilder builder, string value) { builder.Append('"'); foreach (char character in value) { switch (character) { case '"': builder.Append("\\\""); break; case '\\': builder.Append("\\\\"); break; case '\b': builder.Append("\\b"); break; case '\f': builder.Append("\\f"); break; case '\n': builder.Append("\\n"); break; case '\r': builder.Append("\\r"); break; case '\t': builder.Append("\\t"); break; default: if (character < 32) { builder.Append("\\u").Append(((int)character).ToString("x4", CultureInfo.InvariantCulture)); } else { builder.Append(character); } break; } } builder.Append('"'); } private static bool IsNumber(object value) { return value is byte || value is sbyte || value is short || value is ushort || value is int || value is uint || value is long || value is ulong || value is float || value is double || value is decimal; } } } } #endif