This commit is contained in:
2023-11-28 11:41:03 +05:30
commit da3b6cf083
1281 changed files with 97466 additions and 0 deletions

73
Assets/Scripts/Helpers.cs Normal file
View File

@@ -0,0 +1,73 @@
using UnityEngine;
using CustomExtensions;
using System;
public struct InputState{
public int Tick;
public Vector2 Input;
public override string ToString()
{
return $"tick: {Tick}, input: {Input}";
}
public override bool Equals(object obj)
{
InputState _obj = (InputState) obj;
if(_obj.Input == Input){
return true;
}
return false;
}
public bool CloseEnough(InputState other, float threshold) => Mathf.Abs((other.Input-Input).magnitude) < threshold;
}
public struct PlayerState{
public int Tick;
public Vector3 Position;
public Quaternion Rotation;
public override string ToString()
{
return $"tick: {Tick},pos: {Position}, rot: {Rotation}";
}
public override bool Equals(object obj)
{
PlayerState _obj = (PlayerState) obj;
if(_obj.Position == Position && _obj.Rotation == Rotation){
return true;
}
return false;
}
public bool CloseEnough(PlayerState other, float threshold) => Mathf.Abs((other.Position-Position).magnitude) < threshold && Rotation.Approximately(other.Rotation, threshold);
public float Difference(PlayerState other){
return (Mathf.Abs((other.Position-Position).magnitude) + Rotation.Difference(other.Rotation))/2f;
}
}
public class Helpers{
public static int unixTimestamp = (int)System.DateTime.UtcNow.Subtract(new System.DateTime(1970, 1, 1)).TotalSeconds;
}
namespace CustomExtensions{
public static class QuaternionExtensions{
public static bool Approximately(this Quaternion quatA, Quaternion value, float acceptableRange)
{
return 1 - Mathf.Abs(Quaternion.Dot(quatA, value)) < acceptableRange;
}
public static float Difference(this Quaternion quatA, Quaternion value){
return 1 - Mathf.Abs(Quaternion.Dot(quatA, value));
}
public static long ToUnix(this DateTime time){
return (long)time.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7017886b6933ab24ebd07098772bf027
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

67
Assets/Scripts/Logger.cs Normal file
View File

@@ -0,0 +1,67 @@
using System;
using System.IO;
using UnityEngine;
public class Logger
{
public static bool Enabled = true;
private static Logger m_instance = null;
private static string ApplicationDirectory
{
get
{
string path = Application.dataPath;
if (Application.platform == RuntimePlatform.OSXPlayer)
{
path += "/../../";
}
else if (Application.platform == RuntimePlatform.WindowsPlayer)
{
path += "/../";
}
return path;
}
}
public string LogFilePath {get; private set;}
public static Logger instance
{
get
{
if (m_instance == null)
{
m_instance = new Logger();
}
return m_instance;
}
}
public Logger()
{
if(!Enabled){return;}
Debug.Log("Starting logger @ " + ApplicationDirectory);
if(LogFilePath == null){
LogFilePath = ApplicationDirectory + "Log.txt";
}
File.WriteAllText(LogFilePath, "Logger initiated at " + DateTime.Now + "\n\n");
}
public void log(string message){
if(!Enabled){return;}
File.AppendAllText(LogFilePath,$"[{DateTime.Now}] {message}\n");
Debug.Log(message);
}
public static void Log(string message){
instance.log(message);
}
public static void SetFileName(string fileName){
instance.LogFilePath = ApplicationDirectory+fileName + ".txt";
}
public static void SetFilePath(string path){
instance.LogFilePath= path;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 67433c855e8eda8439e0a3ed85f45d60
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

231
Assets/Scripts/Player.cs Normal file
View File

@@ -0,0 +1,231 @@
using UnityEngine;
using Mirror;
using System.Collections.Generic;
using System.Collections;
using CustomExtensions;
public class Player : NetworkBehaviour
{
private float timer;
private int currentTick;
private float minTimeBetweenTicks;
private const float SERVER_TICK_RATE = 30f;
private const int BUFFER_SIZE = 1024;
// Client specific
private StatePayload[] client_stateBuffer;
private InputPayload[] inputBuffer;
private StatePayload latestServerState;
private StatePayload lastProcessedState;
private float horizontalInput;
private float verticalInput;
private StatePayload[] stateBuffer;
private Queue<InputPayload> inputQueue;
void Start()
{
minTimeBetweenTicks = 1f / SERVER_TICK_RATE;
stateBuffer = new StatePayload[BUFFER_SIZE];
inputBuffer = new InputPayload[BUFFER_SIZE];
inputQueue = new Queue<InputPayload>();
}
void Update()
{
horizontalInput = Input.GetAxis("Horizontal");
verticalInput = Input.GetAxis("Vertical");
timer += Time.deltaTime;
while (timer >= minTimeBetweenTicks)
{
timer -= minTimeBetweenTicks;
if(isServer){
HandleTick();
}else if(isLocalPlayer){
HandleTickClient();
}
currentTick++;
}
}
public void OnClientInput(InputPayload inputPayload){
if(isServer){
m_OnClientInput(inputPayload);
}else{
CmdOnClientInput(inputPayload);
}
}
[Command]
public void CmdOnClientInput(InputPayload inputPayload){
m_OnClientInput(inputPayload);
}
public void m_OnClientInput(InputPayload inputPayload)
{
inputQueue.Enqueue(inputPayload);
}
IEnumerator SendToClient(StatePayload statePayload)
{
yield return new WaitForSeconds(0.02f);
OnServerMovementState(statePayload);
}
void HandleTick()
{
// Process the input queue
int bufferIndex = -1;
while(inputQueue.Count > 0)
{
InputPayload inputPayload = inputQueue.Dequeue();
bufferIndex = inputPayload.tick % BUFFER_SIZE;
StatePayload statePayload = ProcessMovement(inputPayload);
stateBuffer[bufferIndex] = statePayload;
}
if (bufferIndex != -1)
{
StartCoroutine(SendToClient(stateBuffer[bufferIndex]));
}
}
void HandleTickClient()
{
if (!latestServerState.Equals(default(StatePayload)) &&
(lastProcessedState.Equals(default(StatePayload)) ||
!latestServerState.Equals(lastProcessedState)))
{
HandleServerReconciliation();
}
int bufferIndex = currentTick % BUFFER_SIZE;
// Add payload to inputBuffer
InputPayload inputPayload = new InputPayload();
inputPayload.tick = currentTick;
inputPayload.inputVector = new Vector3(horizontalInput, 0, verticalInput);
inputBuffer[bufferIndex] = inputPayload;
// Add payload to stateBuffer
stateBuffer[bufferIndex] = ProcessMovement(inputPayload);
// Send input to server
StartCoroutine(SendToServer(inputPayload));
}
void HandleServerReconciliation()
{
lastProcessedState = latestServerState;
int serverStateBufferIndex = latestServerState.tick % BUFFER_SIZE;
float positionError = Vector3.Distance(latestServerState.position, stateBuffer[serverStateBufferIndex].position);
float rotationError = CustomExtensions.QuaternionExtensions.Difference(latestServerState.rotation, stateBuffer[serverStateBufferIndex].rotation);
if (positionError > 0.001f || rotationError > 0.001f)
{
Debug.Log($"We have to reconcile bro, Errors\npos:{positionError}, rot:{rotationError}");
// Rewind & Replay
transform.position = latestServerState.position;
transform.rotation = latestServerState.rotation;
// Update buffer at index of latest server state
stateBuffer[serverStateBufferIndex] = latestServerState;
// Now re-simulate the rest of the ticks up to the current tick on the client
int tickToProcess = latestServerState.tick + 1;
while (tickToProcess < currentTick)
{
int bufferIndex = tickToProcess % BUFFER_SIZE;
// Process new movement with reconciled state
StatePayload statePayload = ProcessMovement(inputBuffer[bufferIndex]);
// Update buffer with recalculated state
stateBuffer[bufferIndex] = statePayload;
tickToProcess++;
}
}
}
public void m_OnServerMovementState(StatePayload serverState)
{
latestServerState = serverState;
}
public void OnServerMovementState(StatePayload serverState){
if(isServer){
RpcOnServerMovementState(serverState);
}else{
m_OnServerMovementState(serverState);
// CmdOnServerMovementState(serverState);
}
}
[ClientRpc]
public void RpcOnServerMovementState(StatePayload serverState){
m_OnServerMovementState(serverState);
Debug.Log(serverState.position + ":" + transform.position);
}
IEnumerator SendToServer(InputPayload inputPayload)
{
yield return new WaitForSeconds(0.02f);
OnClientInput(inputPayload);
}
StatePayload ProcessMovement(InputPayload input)
{
// Should always be in sync with same function on Client
transform.position += input.inputVector * 5f * minTimeBetweenTicks;
transform.Rotate(input.inputVector);
return new StatePayload()
{
tick = input.tick,
position = transform.position,
rotation = transform.rotation
};
}
}
public struct InputPayload
{
public int tick;
public Vector3 inputVector;
public override string ToString()
{
return JsonUtility.ToJson(this);
}
public static InputPayload Parse(string json){
return JsonUtility.FromJson<InputPayload>(json);
}
}
public struct StatePayload
{
public int tick;
public Vector3 position;
public Quaternion rotation;
public override string ToString()
{
return JsonUtility.ToJson(this);
}
public static StatePayload Parse(string json){
return JsonUtility.FromJson<StatePayload>(json);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8682d5131ce872b40a46bbf0d8e273c3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: