Client prediction improved

This commit is contained in:
Sewmina 2022-06-16 02:24:15 +05:30
parent cdaa627f52
commit 102a525c6d
22 changed files with 154 additions and 76 deletions

View File

@ -1,4 +1,7 @@
/Library/**
/Temp/**
/Temp/
/ProjectSettings/ProjectVersion.txt
**.log
**.sln
**.csproj

View File

@ -122,7 +122,9 @@ MonoBehaviour:
turningSmoothFactor: 0.1
joystick: {fileID: 0}
joyInput: {x: 0, y: 0}
bufferSize: 1024
Detour: {x: 0, y: 0, z: 0}
RotationDetour: {x: 0, y: 0, z: 0, w: 1}
DetourCorrectionFactor: 0.1
showDebugHUD: 1
--- !u!114 &5521446474188759663
MonoBehaviour:

View File

@ -38,7 +38,7 @@ RenderSettings:
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_IndirectSpecularColor: {r: 0.44657874, g: 0.49641258, b: 0.5748172, a: 1}
m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
@ -154,7 +154,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
m_Name:
m_EditorClassIdentifier:
Port: 7777
Port: 2500
DualMode: 1
NoDelay: 1
Interval: 10
@ -190,7 +190,7 @@ MonoBehaviour:
offlineScene:
onlineScene:
transport: {fileID: 42724090}
networkAddress: localhost
networkAddress: 38.242.232.13
maxConnections: 100
authenticator: {fileID: 0}
playerPrefab: {fileID: 5431987895376475548, guid: e811a838f2ebb2f4fb8055331ed295e9, type: 3}
@ -664,10 +664,10 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: b20032ec7edc76b0ca5b2759bc19363f, type: 3}
m_Name:
m_EditorClassIdentifier:
autoOffset: 1
offset: {x: 0, y: 0, z: 0}
autoOffset: 0
offset: {x: 0, y: 0, z: -10}
target: {fileID: 0}
smoothness: 10
smoothness: 2.5
--- !u!1 &1012805075
GameObject:
m_ObjectHideFlags: 0

View File

@ -15,7 +15,7 @@ public class CameraFollower : MonoBehaviour
}
// Update is called once per frame
void Update()
void FixedUpdate()
{
if(target==null){return;}
transform.position = Vector3.Lerp(transform.position, target.position + offset, smoothness * Time.deltaTime);

View File

@ -1,4 +1,3 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mirror;
@ -14,13 +13,15 @@ public class SpaceshipController : NetworkBehaviour
[Header("Client Prediction")]
public SortedDictionary<int, StatePayload> serverStateBuffer = new SortedDictionary<int, StatePayload>();
public Vector3 Detour = Vector3.zero;
public Quaternion RotationDetour = Quaternion.identity;
public float DetourCorrectionFactor = 0.5f;
public bool showDebugHUD = false;
void Start()
{
if (isLocalPlayer)
{
if (joystick == null) { joystick = FindObjectOfType<Joystick>(); }
@ -35,6 +36,9 @@ public class SpaceshipController : NetworkBehaviour
// Update is called once per frame
int timeInMillis => (int)(NetworkTime.time * 1000);
int roundedTime => Mathf.FloorToInt((float)timeInMillis / 100f) * 100;
int lastClientUpdateTime = 0;
void FixedUpdate()
{
if (isLocalPlayer)
@ -52,37 +56,59 @@ public class SpaceshipController : NetworkBehaviour
body.rotation = Quaternion.Lerp(body.rotation, Quaternion.AngleAxis(angle1 + 90, Vector3.forward), turningSmoothFactor * joyInput.magnitude);
}
}
if(isLocalPlayer){
///Diff = rot1 . rot2
///1 = rot1 . rot2 / Diff
///1 * Diff * Diff = rot1.rot2. Diff
if (isLocalPlayer)
{
CmdUpdateJoyInput(joyInput);
//Fix Detours
if (Mathf.Abs(Detour.magnitude) > 0.1f)
{
Vector3 newPosition = body.position + Detour;
Quaternion newRotation = body.rotation * RotationDetour;
body.position = Vector3.Lerp(body.position, newPosition, (Mathf.Abs(Detour.magnitude) > 0.2f) ? DetourCorrectionFactor * 2 * Detour.magnitude : DetourCorrectionFactor);
Detour = newPosition - body.position;
body.rotation = Quaternion.Lerp(body.rotation, newRotation, DetourCorrectionFactor * ((joystick.touchDown) ? 0.1f : 1));
RotationDetour = Quaternion.Inverse(transform.rotation) * newRotation;
}
}
//Fill input and position buffer for client predictions
int lastUpdatedTime = 0;
if (timeInMillis % 32 == 0 && lastUpdatedTime != timeInMillis)
{
lastUpdatedTime = timeInMillis;
StatePayload currentState = new StatePayload(timeInMillis, transform.position, transform.rotation, joyInput);
StatePayload currentState = new StatePayload(timeInMillis, transform.position, transform.rotation, joyInput);
if (isLocalPlayer)
if (isLocalPlayer)
{
if (lastClientUpdateTime < roundedTime)
{
CmdValidateMovement(timeInMillis, currentState.Position, currentState.Rotation, currentState.Input);
CmdValidateMovement(roundedTime, currentState.Position, currentState.Rotation, currentState.Input);
lastClientUpdateTime = roundedTime;
}
if (isServer)
}
if (isServer)
{
int lastUpdatedTime = serverStateBuffer.Count > 0 ? serverStateBuffer.Keys.Last() : 0;
if (timeInMillis >= lastUpdatedTime + 100)
{
if (!serverStateBuffer.ContainsKey(timeInMillis))
serverStateBuffer.Add(roundedTime, currentState);
if (serverStateBuffer.Count > 1024)
{
serverStateBuffer.Add(timeInMillis, currentState);
serverStateBuffer.Remove(serverStateBuffer.Keys.First());
}
}
}
}
[Command]
void CmdUpdateJoyInput(Vector2 input){
void CmdUpdateJoyInput(Vector2 input)
{
joyInput = input;
}
@ -90,46 +116,81 @@ public class SpaceshipController : NetworkBehaviour
void CmdValidateMovement(int time, Vector3 position, Quaternion rotation, Vector2 input)
{
StatePayload payload = new StatePayload(time, position, rotation, input);
StatePayload lastServerState = serverStateBuffer.Values.Last();
StatePayload payloadToCompare;
if (serverStateBuffer.Count < 1) { Debug.Log("still Initiating server buffer"); return; }
joyInput = payload.Input;
if (!serverStateBuffer.ContainsKey(time))
{
payloadToCompare = lastServerState;
if (lastServerState.Time < time)
{
Debug.Log("Server doesn't have that data yet\nYou asked for " + time + " best I can do is " + lastServerState.Time);
}
else
{
for (int i = 0; i < 50; i++)
{
if (serverStateBuffer.ContainsKey(time - i))
{
payloadToCompare = serverStateBuffer[time - i];
Debug.Log($"Found closest state at {payloadToCompare.Time}, requested {time}");
break;
}
}
}
if(!serverStateBuffer.ContainsKey(time)){Debug.Log("Server doesn't have that data yet");return;}
}
else
{
payloadToCompare = serverStateBuffer[time];
}
Vector3 positionDiff = payloadToCompare.Position - payload.Position;
int timeDiff = lastServerState.Time - time;
if (serverStateBuffer[time] == payload)
if (Mathf.Abs(positionDiff.magnitude) < 0.2f && lastServerState.Time - time < 500)
{
//Validated, move on
}
else
{
RpcRubberBand(serverStateBuffer.Values.Last().Position,serverStateBuffer.Values.Last().Rotation, NetworkTime.time);
{
Debug.Log($"RB : {positionDiff.magnitude} [{timeDiff}ms]");
RpcRubberBand(serverStateBuffer.Values.Last().Position, serverStateBuffer.Values.Last().Rotation, timeInMillis);
}
}
void ValidateCSBuffer()
{
}
double lastRubberBandTime = 0;
[ClientRpc]
void RpcRubberBand(Vector3 position, Quaternion rotation, double sentTime)
{
if (isLocalPlayer)
{
if (sentTime < lastRubberBandTime) { Debug.Log("Old rubber band rpc, ignoree..."); return; }
//Lag comprehension
double delay = NetworkTime.time - sentTime;
transform.position = position;
transform.Translate(new Vector3(0, movingSpeed * ((float)delay / 0.02f)));
transform.rotation = rotation;
double delay = timeInMillis - sentTime + 10;
Vector3 newPosition = position + new Vector3(0, movingSpeed * ((float)(delay) / 20f));
Detour = newPosition - transform.position;
// RotationDetour = rotation;
RotationDetour = Quaternion.Inverse(transform.rotation) * rotation;
lastRubberBandTime = sentTime;
Debug.Log($"Rubber banded (Detour of {Detour}) you to {transform.position}, @ {sentTime} (delay: {delay}");
}
}
void OnGUI()
{
if (!isLocalPlayer) { return; }
if (showDebugHUD)
{
GUI.Label(new Rect(Screen.width - 120, 10, 100, 20), transform.position.ToString());
GUI.Label(new Rect(Screen.width - 120, 30, 100, 20), timeInMillis.ToString());
GUI.Label(new Rect(Screen.width - 100, Screen.height - 30, 50, 20), (NetworkTime.rtt * 1000).ToString() + " ms");
}
}

90
upf.sln
View File

@ -1,63 +1,75 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp", "Assembly-CSharp.csproj", "{d2fa7125-b336-b292-eceb-bf1e731b3f48}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp", "Assembly-CSharp.csproj", "{f8f3d981-3ac9-a8b7-ea43-2868619efe2a}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleWebTransport", "SimpleWebTransport.csproj", "{5c2c6f8b-afbb-9cd0-d973-6efba6c32d6d}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleWebTransport", "SimpleWebTransport.csproj", "{f4ef7e8d-99b4-a202-8a27-32dcfce49cfe}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror", "Mirror.csproj", "{8c789ebf-5061-692d-09a2-395c3f960186}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror.Examples", "Mirror.Examples.csproj", "{6f1ac7da-277d-ee44-8547-e218c93f84df}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror.Examples", "Mirror.Examples.csproj", "{bd29b6d7-a588-6db2-c4b4-0c3e7050329f}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror", "Mirror.csproj", "{d9448b0f-e456-cf8f-8285-980f3496df94}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror.Components", "Mirror.Components.csproj", "{92ca8eda-4591-6ba7-62fe-23737b4dad83}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Whinarn.UnityMeshSimplifier.Runtime", "Whinarn.UnityMeshSimplifier.Runtime.csproj", "{b99ffb8c-f6f2-f646-802e-7cb2e62d8c0c}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "kcp2k", "kcp2k.csproj", "{c8f5758f-6f4d-6ca3-8672-ff5589388f80}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror.Components", "Mirror.Components.csproj", "{0203a8e7-6255-7e0b-3cbf-ecae2398312c}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telepathy", "Telepathy.csproj", "{a36d2e59-4ac3-f799-3987-fc2485d79e1a}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telepathy", "Telepathy.csproj", "{8c94d811-7988-2ebf-1cc0-26b574688e67}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "where-allocations", "where-allocations.csproj", "{ebda325d-e86f-a93b-6742-1169bded4621}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "kcp2k", "kcp2k.csproj", "{cf7f2fab-d83d-f366-f3b2-9d9e4eddc3cc}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror.Authenticators", "Mirror.Authenticators.csproj", "{3eef3b8e-d897-623d-b9c7-d540c823b9a8}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "where-allocations", "where-allocations.csproj", "{e020c22e-60bb-7a19-f116-ef587d3ae77f}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp-Editor", "Assembly-CSharp-Editor.csproj", "{11a3099b-b691-e713-f90d-1e4ea21c7a4c}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror.Authenticators", "Mirror.Authenticators.csproj", "{520da678-0ed5-7053-5e7f-1a7e3c90b70a}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror.Editor", "Mirror.Editor.csproj", "{2c2a6066-189f-5a3f-0f94-acebe3ed3a33}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Mirror.CodeGen", "Unity.Mirror.CodeGen.csproj", "{58b849ad-b7ea-e401-5c5b-0056f4cec462}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Mirror.CodeGen", "Unity.Mirror.CodeGen.csproj", "{0568cbc7-4b38-de37-2cfc-efd6fc69d0df}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror.Editor", "Mirror.Editor.csproj", "{74cb33d9-aafa-4b6f-1603-b7b7c41edd4c}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror.CompilerSymbols", "Mirror.CompilerSymbols.csproj", "{f12b632e-c0f2-40d4-ee68-81bde8ff4e6e}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror.CompilerSymbols", "Mirror.CompilerSymbols.csproj", "{e1d3e786-6173-5f3a-7812-807511d3f547}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.com.consulo.ide.Editor", "Unity.com.consulo.ide.Editor.csproj", "{f3918ae7-b834-5fa8-e000-01833c449c8e}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp-Editor", "Assembly-CSharp-Editor.csproj", "{20764e99-32a2-613b-c7bd-2b420a48759f}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Whinarn.UnityMeshSimplifier.Editor", "Whinarn.UnityMeshSimplifier.Editor.csproj", "{9c3e4793-780d-e333-1ec5-d5e17feec6f2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{d2fa7125-b336-b292-eceb-bf1e731b3f48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{d2fa7125-b336-b292-eceb-bf1e731b3f48}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5c2c6f8b-afbb-9cd0-d973-6efba6c32d6d}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5c2c6f8b-afbb-9cd0-d973-6efba6c32d6d}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8c789ebf-5061-692d-09a2-395c3f960186}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8c789ebf-5061-692d-09a2-395c3f960186}.Debug|Any CPU.Build.0 = Debug|Any CPU
{bd29b6d7-a588-6db2-c4b4-0c3e7050329f}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{bd29b6d7-a588-6db2-c4b4-0c3e7050329f}.Debug|Any CPU.Build.0 = Debug|Any CPU
{92ca8eda-4591-6ba7-62fe-23737b4dad83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{92ca8eda-4591-6ba7-62fe-23737b4dad83}.Debug|Any CPU.Build.0 = Debug|Any CPU
{c8f5758f-6f4d-6ca3-8672-ff5589388f80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{c8f5758f-6f4d-6ca3-8672-ff5589388f80}.Debug|Any CPU.Build.0 = Debug|Any CPU
{a36d2e59-4ac3-f799-3987-fc2485d79e1a}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{a36d2e59-4ac3-f799-3987-fc2485d79e1a}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ebda325d-e86f-a93b-6742-1169bded4621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ebda325d-e86f-a93b-6742-1169bded4621}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3eef3b8e-d897-623d-b9c7-d540c823b9a8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3eef3b8e-d897-623d-b9c7-d540c823b9a8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11a3099b-b691-e713-f90d-1e4ea21c7a4c}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11a3099b-b691-e713-f90d-1e4ea21c7a4c}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2c2a6066-189f-5a3f-0f94-acebe3ed3a33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2c2a6066-189f-5a3f-0f94-acebe3ed3a33}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0568cbc7-4b38-de37-2cfc-efd6fc69d0df}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0568cbc7-4b38-de37-2cfc-efd6fc69d0df}.Debug|Any CPU.Build.0 = Debug|Any CPU
{f12b632e-c0f2-40d4-ee68-81bde8ff4e6e}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{f12b632e-c0f2-40d4-ee68-81bde8ff4e6e}.Debug|Any CPU.Build.0 = Debug|Any CPU
{f8f3d981-3ac9-a8b7-ea43-2868619efe2a}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{f8f3d981-3ac9-a8b7-ea43-2868619efe2a}.Debug|Any CPU.Build.0 = Debug|Any CPU
{f4ef7e8d-99b4-a202-8a27-32dcfce49cfe}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{f4ef7e8d-99b4-a202-8a27-32dcfce49cfe}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6f1ac7da-277d-ee44-8547-e218c93f84df}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6f1ac7da-277d-ee44-8547-e218c93f84df}.Debug|Any CPU.Build.0 = Debug|Any CPU
{d9448b0f-e456-cf8f-8285-980f3496df94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{d9448b0f-e456-cf8f-8285-980f3496df94}.Debug|Any CPU.Build.0 = Debug|Any CPU
{b99ffb8c-f6f2-f646-802e-7cb2e62d8c0c}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{b99ffb8c-f6f2-f646-802e-7cb2e62d8c0c}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0203a8e7-6255-7e0b-3cbf-ecae2398312c}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0203a8e7-6255-7e0b-3cbf-ecae2398312c}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8c94d811-7988-2ebf-1cc0-26b574688e67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8c94d811-7988-2ebf-1cc0-26b574688e67}.Debug|Any CPU.Build.0 = Debug|Any CPU
{cf7f2fab-d83d-f366-f3b2-9d9e4eddc3cc}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{cf7f2fab-d83d-f366-f3b2-9d9e4eddc3cc}.Debug|Any CPU.Build.0 = Debug|Any CPU
{e020c22e-60bb-7a19-f116-ef587d3ae77f}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{e020c22e-60bb-7a19-f116-ef587d3ae77f}.Debug|Any CPU.Build.0 = Debug|Any CPU
{520da678-0ed5-7053-5e7f-1a7e3c90b70a}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{520da678-0ed5-7053-5e7f-1a7e3c90b70a}.Debug|Any CPU.Build.0 = Debug|Any CPU
{58b849ad-b7ea-e401-5c5b-0056f4cec462}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{58b849ad-b7ea-e401-5c5b-0056f4cec462}.Debug|Any CPU.Build.0 = Debug|Any CPU
{74cb33d9-aafa-4b6f-1603-b7b7c41edd4c}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{74cb33d9-aafa-4b6f-1603-b7b7c41edd4c}.Debug|Any CPU.Build.0 = Debug|Any CPU
{e1d3e786-6173-5f3a-7812-807511d3f547}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{e1d3e786-6173-5f3a-7812-807511d3f547}.Debug|Any CPU.Build.0 = Debug|Any CPU
{f3918ae7-b834-5fa8-e000-01833c449c8e}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{f3918ae7-b834-5fa8-e000-01833c449c8e}.Debug|Any CPU.Build.0 = Debug|Any CPU
{20764e99-32a2-613b-c7bd-2b420a48759f}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{20764e99-32a2-613b-c7bd-2b420a48759f}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9c3e4793-780d-e333-1ec5-d5e17feec6f2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9c3e4793-780d-e333-1ec5-d5e17feec6f2}.Debug|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE