public override void NotifyVerticalSliceBundleLoaded(VerticalActivitySlice[] slices)
        {
            base.NotifyVerticalSliceBundleLoaded(slices);

            if (fpsInfoGraph != null)
                fpsInfoGraph.UpdateDataBundle(slices);

            if (memoryInfoGraph != null)
                memoryInfoGraph.UpdateDataBundle(slices);
        }
 protected override string CollectHoverData(VerticalActivitySlice slice)
 {
     float div = 1048576;
     string result = "Textures 2D/3D:  " +   (slice.systemInfo.activeTextures != -1 ?        (slice.systemInfo.activeTextures        + " [" + (slice.systemInfo.activeTextureMemory / div) + " mb]\n")       : "n/a\n");
     result += "Render textures:  " +        (slice.systemInfo.activeRenderTextures != -1 ?  (slice.systemInfo.activeRenderTextures  + " [" + (slice.systemInfo.activeRenderTextureMemory / div) + " mb]\n") : "n/a\n");
     result += "Audio clips:  " +            (slice.systemInfo.activeAudioClips != -1 ?      (slice.systemInfo.activeAudioClips      + " [" + (slice.systemInfo.activeAudioClipMemory / div) + " mb]\n")     : "n/a\n");
     result += "Animation clips:  " +        (slice.systemInfo.activeAnimations != -1 ?      (slice.systemInfo.activeAnimations      + " [" + (slice.systemInfo.activeAnimationMemory/ div) + " mb]\n")      : "n/a\n");
     result += "Mesh data:  " +              (slice.systemInfo.activeMeshes != -1 ?          (slice.systemInfo.activeMeshes          + " [" + (slice.systemInfo.activeMeshMemory / div) + " mb]\n")          : "n/a\n");
     result += "Materials:  " +              (slice.systemInfo.activeMaterials != -1 ?       (slice.systemInfo.activeMaterials       + " [" + (slice.systemInfo.activeMaterialsMemory / div) + " mb]\n")     : "n/a\n");
     return result;
 }
 protected override float[] GetSliceGraphData(VerticalActivitySlice slice)
 {
     return new float[]
     {
         slice.systemInfo.activeTextureMemory,
         slice.systemInfo.activeRenderTextureMemory,
         slice.systemInfo.activeAudioClipMemory,
         slice.systemInfo.activeAnimationMemory,
         slice.systemInfo.activeMeshMemory,
         slice.systemInfo.activeMaterialsMemory
     };
 }
        public static SMesh GetMeshFromSlice(VerticalActivitySlice slice, int meshGoInstanceID)
        {
            if (slice == null || slice.meshData == null)
                return null;

            foreach (SWorldObject wo in slice.meshData.worldObjects) {
                if (wo == null || wo.meshes == null)
                    continue;

                foreach (SMesh mesh in wo.meshes) {
                    if (mesh != null && mesh.header.goInstanceID == meshGoInstanceID)
                        return mesh;
                }
            }
            return null;
        }
        public static VerticalActivitySlice Deserialize(byte[] input)
        {
            ComposedByteStream stream = ComposedByteStream.FromByteArray(input);
            if (stream == null)
                return null;

            VerticalActivitySlice result = new VerticalActivitySlice();

            result.header = VerticalSliceHeaderData.Deserialize(stream.ReadNextStream<byte>());
            result.systemInfo = SystemInfoSnapshot.Deserialize(stream.ReadNextStream<byte>());
            result.debugLogs = DebugLogSnapshot.Deserialize(stream.ReadNextStream<byte>());
            result.humanInput = HumanInputSnapshot.Deserialize(stream.ReadNextStream<byte>());
            result.meshData = MeshDataSnapshot.Deserialize(stream.ReadNextStream<byte>());
            result.particleData = ParticleDataSnapshot.Deserialize(stream.ReadNextStream<byte>());
            result.materialData = MaterialDataSnapshot.Deserialize(stream.ReadNextStream<byte>());
            result.gameObjectsSnapshot = GameObjectsSnapshot.Deserialize(stream.ReadNextStream<byte>());
            result.screenCapture = ScreenCaptureSnapshot.Deserialize(stream.ReadNextStream<byte>());

            return result;
        }
        public ExposedGameObject[] GetChildren(int instanceID, VerticalActivitySlice slice)
        {
            if (slice == null || slice.gameObjectsSnapshot == null || slice.gameObjectsSnapshot.gameObjects == null)
                return null;

            long key = vBugMathHelper.CombineInts(instanceID, slice.header.frameNumber);
            if (childrenPerGO.ContainsKey(key)) {
                return childrenPerGO[key];
            } else {
                List<ExposedGameObject> result = new List<ExposedGameObject>();
                ExposedGameObject[] allGameObjects = slice.gameObjectsSnapshot.gameObjects;
                int i = allGameObjects.Length;
                while (--i > -1) {
                    if (allGameObjects[i].parentID == instanceID)
                        result.Add(allGameObjects[i]);
                }

                ExposedGameObject[] resultArray = result.ToArray();
                childrenPerGO.Add(key, resultArray);
                return resultArray;
            }
        }
        public ExposedComponent[] GetComponents(ExposedGameObject go, VerticalActivitySlice slice)
        {
            if (go == null || go.components == null)
                return null;

            if (slice == null || slice.gameObjectsSnapshot == null || slice.gameObjectsSnapshot.components == null)
                return null;

            long key = vBugMathHelper.CombineInts(go.transformID, slice.header.frameNumber);
            if (componentsPerGO.ContainsKey(key)) {
                return componentsPerGO[key];
            } else {
                int i = go.components.Length;
                ExposedComponent[] allComponents = slice.gameObjectsSnapshot.components;
                ExposedComponent[] result = new ExposedComponent[i];
                while (--i > -1)
                    result[i] = allComponents[go.components[i]];

                componentsPerGO.Add(key, result);
                return result;
            }
        }
        public override void DrawQuads(Transform parent, Vector3 localScale, VerticalActivitySlice slice)
        {
            if (slice == null || slice.humanInput == null) {
                DestroyAllQuads();
                return;
            }

            if (mouseNormal == null)
                InitQuads();

            foreach (HumanInputProviderData provider in slice.humanInput.providersData){
                if (provider == null)
                    continue;

                if (provider.providerType == "MouseInputProvider") {
                    if (provider.basicInput != null && provider.basicInput.Length > 0)
                        DrawMouseQuads(provider, parent, slice.header.screenWidth, slice.header.screenHeight);

                    break;
                }
            }
            return;
        }
 public void UpdateDataBundle(VerticalActivitySlice[] bundle)
 {
     UpdateMouseOverInfo(bundle);
     canvasDirty = true;
 }
        public override void DrawSettingsLayout(VerticalActivitySlice slice, bool showAlpha)
        {
            base.DrawSettingsLayout(slice, showAlpha);

            Rect icon = GUILayoutUtility.GetRect(32, 32);
            VisualResources.DrawIcon(EditorIcon.playbackOptions, 3, new Vector2(icon.x, icon.y + 5), 1f, false);

            //--------------- Check cam names --------------------
            if (slice != null && slice.screenCapture != null && slice.screenCapture.camNames != null) {
                foreach (string name in slice.screenCapture.camNames) {
                    if (!camNames.Contains(name)) {
                        camNames.Add(name);
                        camStates.Add(true);
                    }
                }
            }
            //--------------- Check cam names --------------------

            //--------------- Draw toggles --------------------
            scrollPos = GUILayout.BeginScrollView(scrollPos);
            GUILayout.BeginHorizontal();

            if (camNames.Count > 0) {
                for (int i = 0; i < camNames.Count; i++)
                    camStates[i] = GUILayout.Toggle(camStates[i], camNames[i], EditorHelper.styleToggle);

                if (GUI.changed)
                    isDirty = true;
            }

            GUILayout.FlexibleSpace();
            GUILayout.EndHorizontal();
            GUILayout.EndScrollView();
            //--------------- Draw toggles --------------------
        }
 public virtual void DrawSettingsLayout(VerticalActivitySlice slice, bool showAlpha)
 {
     this.showAlpha = showAlpha;
 }
        public override void NotifyVerticalSliceBundleLoaded(VerticalActivitySlice[] slices)
        {
            base.NotifyVerticalSliceBundleLoaded(slices);

            UpdateOverlays(vBugWindowMediator.currentFrameNumber);
            Repaint();
        }
        private void DrawSettingsOptions(float startY, VerticalActivitySlice slice)
        {
            VisualResources.DrawTexture(new Rect(0, startY, this.position.width, optionsBarHeight), VisualResources.proConsoleRowDark);
            Rect layoutRect = new Rect(0f, startY, this.position.width, dragBarRect.y - startY);
            GUILayout.BeginArea(layoutRect);
            GUILayout.BeginHorizontal();

            //--------------- Rotations options --------------------
            //GUILayout.Space(45);
            Rect icon = GUILayoutUtility.GetRect(40, optionsBarHeight);
            VisualResources.DrawIcon(EditorIcon.playbackOptions, 1, new Vector2(icon.x + 10, icon.y + 5), 1f, false);

            GUILayout.BeginVertical(GUILayout.Width(100));
            GUILayout.Space(5);

            rotOptionIdx = EditorGUILayout.Popup(rotOptionIdx, rotationsOptions, EditorHelper.stylePopup);
            rotationMode = (PlaybackScreenRotationMode)rotOptionIdx;

            GUILayout.BeginHorizontal();
            GUI.enabled = rotationMode != PlaybackScreenRotationMode.none;
            rotationZOnly = GUILayout.Toggle(rotationZOnly, "'Z' axis only", EditorHelper.styleToggle);
            GUI.enabled = true;

            showAlpha = GUILayout.Toggle(showAlpha, "alpha", EditorHelper.styleToggle);
            GUILayout.EndHorizontal();

            GUILayout.EndVertical();
            //--------------- Rotations options --------------------

            //--------------- VertBar --------------------
            GUILayout.Space(10);
            Rect vertBar = GUILayoutUtility.GetRect(5, optionsBarHeight);
            VisualResources.DrawTexture(new Rect(vertBar.x, vertBar.y +5, 5, optionsBarHeight - 10), VisualResources.proConsoleRowLight);
            GUILayout.Space(10);
            //--------------- VertBar --------------------

            //--------------- Overlay options --------------------
            foreach (BasePlaybackOverlay overlay in overlays) {
                overlay.DrawSettingsLayout(slice, showAlpha);
            }
            //--------------- Overlay options --------------------

            GUILayout.EndHorizontal();
            GUILayout.EndArea();
        }
        private void UpdateMouseOverInfo(VerticalActivitySlice[] bundle)
        {
            foreach (VerticalActivitySlice slice in bundle) {
                if (!ValidateSlice(slice))
                    continue;

                string info = CollectHoverData(slice);
                if (mouseOverInfo.ContainsKey(slice.header.frameNumber)) {
                    mouseOverInfo[slice.header.frameNumber] = info;
                } else {
                    mouseOverInfo.Add(slice.header.frameNumber, info);
                }
            }
        }
        private void DrawGameObjectsRecursive(VerticalActivitySlice slice, ExposedGameObject[] gameObjects, bool parentActive, bool allowMouseClick)
        {
            EventType eventType = Event.current.type;
            GUILayout.BeginVertical();

            Rect lastRect = default(Rect);

            foreach (ExposedGameObject go in gameObjects) {
                ExposedGameObject[] children = GetChildren(go.transformID, slice);

                bool isFoldout = children != null && children.Length > 0;

                //--------------- Draw recursive --------------------
                if (!hierarchyFoldoutStates.ContainsKey(go.transformID))
                    hierarchyFoldoutStates.Add(go.transformID, false);

                bool localActive = parentActive && go.activeSelf;
                string label = go.name;
                GUI.color = localActive ? Color.white : new Color(0.6f, 0.6f, 0.6f);

                if (go.isHiddenOrDontSave && go.transformID != nextSelectedID) {
                    GUI.color = new Color(0.1f, 0.1f, 0.1f);
                    label = "[hidden]" + label;
                } else if (go.isStatic) {
                    GUI.color = new Color(0.8f, 0.8f, 0.8f);
                    label = "[static] " + label;
                } else {
                    GUI.color = Color.white;
                }

                string currentControlName = "hierarchy_" + go.transformID;
                GUI.SetNextControlName(currentControlName);
                controllIndexing.Add(currentControlName);

                bool drawInspIcon = eventType == EventType.repaint && go.components != null && go.components.Length > 0;
                bool drawEOLIcon = TestEOL(go.transformID, slice.header.frameNumber);
                bool drawBirthIcon = TestBirth(go.transformID, slice.header.frameNumber);
                int offset = 0;

                if (isFoldout) {
                    bool newState = EditorGUILayout.Foldout(hierarchyFoldoutStates[go.transformID], label, EditorHelper.styleHierarchyFoldout);
                    lastRect = GUILayoutUtility.GetLastRect();

                    if (newState != hierarchyFoldoutStates[go.transformID]) {
                        SetNextSelection(go.transformID);
                        if (newState)
                            SortReflectedObjectsByName(children);
                    }

                    hierarchyFoldoutStates[go.transformID] = newState;
                    if (newState) {
                        GUILayout.BeginHorizontal();
                        GUILayout.Space(16);
                        DrawGameObjectsRecursive(slice, children, localActive, allowMouseClick);
                        GUILayout.EndHorizontal();
                    }

                    if (hasFocus && eventType == EventType.mouseUp && GUI.GetNameOfFocusedControl() == currentControlName)
                        SetNextSelection(go.transformID);

                } else {
                    GUILayout.BeginHorizontal();
                    GUILayout.Space(18);
                    offset = 14;

                    if (GUILayout.Button(label, EditorHelper.styleHierarchyLabel, GUILayout.Height(16)))
                        SetNextSelection(go.transformID);

                    lastRect = GUILayoutUtility.GetLastRect();
                    GUILayout.EndHorizontal();
                }

                if (drawInspIcon)
                    VisualResources.DrawIcon(EditorIcon.hierarchyAndInspector, 1, new Vector2(lastRect.x - 12 - offset, lastRect.y + 2), 1f, false);

                if (drawEOLIcon)
                    VisualResources.DrawIcon(EditorIcon.hierarchyAndInspector, 3, new Vector2(lastRect.x - 24 - offset, lastRect.y + 2), 1f, false);

                if (drawBirthIcon)
                    VisualResources.DrawIcon(EditorIcon.hierarchyAndInspector, 0, new Vector2(lastRect.x - 36 - offset, lastRect.y + 2), 1f, false);
                //--------------- Draw recursive --------------------

            }

            GUILayout.EndVertical();
            GUI.color = GUI.contentColor = GUI.backgroundColor = Color.white;
        }
 public override void NotifyVerticalSliceBundleLoaded(VerticalActivitySlice[] slices)
 {
     base.NotifyVerticalSliceBundleLoaded(slices);
     ClearDictionaries();
     SetCurrentSlice(vBugWindowMediator.currentFrameNumber);
 }
 public override void NotifyVerticalSliceBundleLoaded(VerticalActivitySlice[] slices)
 {
     base.NotifyVerticalSliceBundleLoaded(slices);
     RebuildCachedData(slices);
     Repaint();
 }
        private void RebuildCachedData(VerticalActivitySlice[] bundle)
        {
            if (bundle == null || bundle.Length == 0)
                return;

            bool doUpdate = false;
            for (int i = 0; i < bundle.Length; i++) {
                VerticalActivitySlice slice = bundle[i];
                if (slice == null || slice.debugLogs == null)
                    continue;

                //--------------- Remove if already present --------------------
                int doubleIdx = cachedCallsData.FindIndex(item => item.frameNumber == slice.header.frameNumber);
                if (doubleIdx != -1)
                    cachedCallsData.RemoveAt(doubleIdx);
                //--------------- Remove if already present --------------------

                //--------------- Add new! --------------------
                if (slice.debugLogs.calls.Length > 0) {
                    int jMax = slice.debugLogs.calls.Length;
                    string[] niceTimeStamps = new string[jMax];
                    for (int j = 0; j < jMax; j++)
                        niceTimeStamps[j] = vBugEnvironment.ToUnixDateTime(slice.debugLogs.calls[j].unixTimeStamp).ToString("yyyy-MM-dd  HH:mm:ss:fff");

                    string niceFrameNumber = vBugEnvironment.GetNiceFrameNumber(slice.header.frameNumber);

                    cachedCallsData.Add(new CallCachdeData() {
                        calls = slice.debugLogs.calls,
                        frameNumber = slice.header.frameNumber,
                        niceFrameNumber = niceFrameNumber,
                        niceTimeStamp = niceTimeStamps
                    });

                    doUpdate = true;
                }
                //--------------- Add new! --------------------
            }

            //--------------- Sort & Update position --------------------
            if (doUpdate) {
                cachedCallsData.Sort((a, b) => a.frameNumber.CompareTo(b.frameNumber));

                int totalHeight = 0;
                foreach (CallCachdeData call in cachedCallsData) {
                    int newHeight = call.calls.Length * ElementHeight;
                    call.height = newHeight;
                    call.y = totalHeight;
                    totalHeight += newHeight;
                }
            }
            //--------------- Sort & Update position --------------------
        }
        public override void DrawQuads(Transform parent, Vector3 localScale, VerticalActivitySlice slice)
        {
            if(slice == null)
                return;

            if (isDirty || screenCaptureFrameNr != slice.header.frameNumber || quads.Count == 0)
            {
                if (slice.screenCapture != null && slice.screenCapture.camRenders != null && slice.screenCapture.camRenders.Length != 0)
                {
                    isDirty = false;
                    screenCaptureFrameNr = slice.header.frameNumber;
                    DisposeTextures();

                    int iMax = slice.screenCapture.camRenders.Length;
                    if (quads.Count > iMax)
                    {
                        int i = quads.Count;
                        while (--i > iMax - 1)
                        {
                            UnityEngine.Object.DestroyImmediate(quads[i]);
                            quads.RemoveAt(i);
                        }
                    }
                    else if (quads.Count < iMax)
                    {
                        for (int i = quads.Count; i < iMax; i++)
                        {
                            GameObject quad = CreateQuad();
                            Transform qTransform = quad.transform;
                            qTransform.parent = parent;
                            qTransform.localPosition = Vector3.zero;
                            qTransform.localRotation = Quaternion.identity;
                            quads.Add(quad);
                        }
                    }

                    for (int i = 0; i < iMax; i++)
                    {
                        ScreenCaptureSnapshot screenShot = slice.screenCapture;
                        Texture2D texture = TextureHelper.SetBytes(screenShot.camRenders[i], showAlpha && i != 0, PlayerSettings.colorSpace == ColorSpace.Linear);
                        textures.Add(texture);

                        Rect rect = screenShot.camRects[i];
                        GameObject quad = quads[i];

                        Renderer renderer = quad.GetComponent<Renderer>();
                        if (renderer != null)
                            renderer.sharedMaterial.SetTexture("_MainTex", texture);

                        Vector2 size = Vector3.Scale(localScale, new Vector2(rect.width, rect.height));
                        Vector2 screenPos = new Vector2(-localScale.x, -localScale.y) / 2f; //Center == Left bottom corner
                        screenPos += (size / 2f); //makes it fullscreen if size = 1x1;
                        screenPos += new Vector2( //Adds the offset of the camRect accordingly
                            rect.x * localScale.x,
                            rect.y * localScale.y
                        );

                        quad.transform.localScale = size;
                        quad.transform.localPosition = screenPos;

                        string name = screenShot.camNames[i];
                        if (camNames.Contains(name))
                            quad.SetActive(camStates[camNames.IndexOf(name)]);
                    }
                }
            }
        }
 protected virtual string CollectHoverData(VerticalActivitySlice slice)
 {
     throw new NotImplementedException();
 }
 protected virtual float[] GetSliceGraphData(VerticalActivitySlice slice)
 {
     throw new NotImplementedException();
 }
        private int FindScrollPosByID(ExposedGameObject[] gameObjects, VerticalActivitySlice slice, int instanceID, int currentScrollPos, ref ExposedGameObject selectedGO)
        {
            if (gameObjects == null || instanceID == -1) {
                selectedGO = null;
                return -1;
            }

            //--------------- Scan children second --------------------
            int offset = 0;
            int increment = 16;
            foreach (ExposedGameObject obj in gameObjects) {
                if (obj.transformID == instanceID) {
                    selectedGO = obj;
                    return currentScrollPos + offset;
                } else {
                    ExposedGameObject[] children = GetChildren(obj.transformID, slice);
                    if (children != null && children.Length > 0) {
                        increment = 18;
                        int result = FindScrollPosByID(children, slice, instanceID, currentScrollPos + offset + increment, ref selectedGO);
                        if (result != -1) {
                            if (hierarchyFoldoutStates.ContainsKey(obj.transformID)) {
                                hierarchyFoldoutStates[obj.transformID] = true;
                            } else {
                                hierarchyFoldoutStates.Add(obj.transformID, true);
                            }
                            return result;
                        }
                    } else {
                        increment = 16;
                    }
                }
                offset += increment;
            }
            //--------------- Scan children second --------------------
            return -1;
        }
        private bool ValidateSlice(VerticalActivitySlice slice)
        {
            if (slice != null && slice.systemInfo != null) {
                if (requiresMemoryCapture) {
                    if (slice.systemInfo.memoryProfiled)
                        return true;

                } else {
                    return true;
                }
            }
            return false;
        }
        private void SetCurrentSlice(int frameNumber)
        {
            currentSlice = null;
            int iMin = Mathf.Max(VerticalSliceDatabase.minRange, frameNumber - vBugEditorSettings.PlaybackHierarchySearchRange);

            for (int i = frameNumber; i >= iMin; i--) {
                currentSlice = VerticalSliceDatabase.GetSlice(i);

                if (currentSlice != null && currentSlice.gameObjectsSnapshot != null && currentSlice.gameObjectsSnapshot.gameObjects != null) {
                    currentSelection = null;
                    break;
                }
            }
        }
        private void UpdateCameraSettings(Transform quadParent, VerticalActivitySlice slice, Vector2 localScale)
        {
            if (slice == null || slice.screenCapture == null || cam == null)
                return;

            ScreenCaptureSnapshot sc = slice.screenCapture;
            Vector3 rotation = Vector3.zero;
            cam.fieldOfView = 70f;
            switch (rotationMode) {
                case PlaybackScreenRotationMode.none:
                    cam.orthographic = true;
                    cam.orthographicSize = (localScale.y / 2f) + 0.01f; //(Mathf.Min(localScale.x, localScale.y) / 2f) + 0.01f;
                    break;
                case PlaybackScreenRotationMode.posMainCam:
                    cam.orthographic = false;
                    rotation = sc.mainCamWorldRotation;
                    break;

                case PlaybackScreenRotationMode.negMainCam:
                    cam.orthographic = false;
                    rotation = -(Vector3)sc.mainCamWorldRotation;
                    break;

                case PlaybackScreenRotationMode.posAcceleration:
                    cam.orthographic = false;
                    rotation = sc.inputAcceleration;
                    break;

                case PlaybackScreenRotationMode.negAcceleration:
                    cam.orthographic = false;
                    rotation = -(Vector3)sc.inputAcceleration;
                    break;
            }

            if (rotationZOnly)
                rotation.x = rotation.y = 0f;

            quadParent.eulerAngles = rotation;
        }
        public static void NotifyVerticalSliceBundleLoaded(VerticalActivitySlice[] slices)
        {
            if (EditorApplication.isPlaying || EditorApplication.isPlayingOrWillChangePlaymode)
                return;

            foreach (vBugBaseWindow window in vBugBaseWindow.GetAllWindows())
                window.NotifyVerticalSliceBundleLoaded(slices);
        }
 public virtual void DrawQuads(Transform parent, Vector3 localScale, VerticalActivitySlice slice)
 {
     throw new NotImplementedException();
 }
        public ExposedGameObject[] GetRootObjects(VerticalActivitySlice slice)
        {
            if (slice == null || slice.gameObjectsSnapshot == null || slice.gameObjectsSnapshot.gameObjects == null)
                return null;

            if (rootGOs.ContainsKey(slice.header.frameNumber)) {
                return rootGOs[slice.header.frameNumber];
            } else {
                ExposedGameObject[] allGameObjects = slice.gameObjectsSnapshot.gameObjects;
                List<ExposedGameObject> result = new List<ExposedGameObject>();
                int i = allGameObjects.Length;

                while (--i > -1) {
                    if (allGameObjects[i].parentID == -1)
                        result.Add(allGameObjects[i]);
                }

                ExposedGameObject[] resultArray = result.ToArray();
                SortReflectedObjectsByName(resultArray);
                rootGOs.Add(slice.header.frameNumber, resultArray);
                return resultArray;
            }
        }
            public void Dispose()
            {
                if (slice != null)
                    slice.Dispose();

                slice = null;
                path = null;
            }
            public void Prune()
            {
                if (slice != null)
                    slice.Dispose();

                slice = null;
                state = State.idle;
                requestTimeStamp = 0;
            }