/// <summary> /// Fill a stream with relevant contents of directory tree for hashing. Directory tree is filtered by /// inputParams if non-null. /// </summary> private static void BuildCRCStream(string directory, InputParams inputParams, ref MemoryStream memoryStream) { DirectoryInfo directoryInfo = new DirectoryInfo(directory); SyncResults results = new SyncResults(); // get filtered list of files in this directory FileInfo[] files = new Sync().GetFiles(directoryInfo, inputParams, ref results); // sort by name for deterministic order Array.Sort(files, new FileInfoComparer()); // write information about each file to stream foreach (FileInfo fileInfo in files) { byte[] bytes = ASCIIEncoding.UTF8.GetBytes(fileInfo.Name); memoryStream.Write(bytes, 0, bytes.Length); bytes = BitConverter.GetBytes(fileInfo.Length); memoryStream.Write(bytes, 0, bytes.Length); bytes = BitConverter.GetBytes(fileInfo.LastWriteTime.ToBinary()); memoryStream.Write(bytes, 0, bytes.Length); bytes = ASCIIEncoding.UTF8.GetBytes(fileInfo.Attributes.ToString()); memoryStream.Write(bytes, 0, bytes.Length); } // get filtered list of subdirectories DirectoryInfo[] subdirs = new Sync().GetDirectories(directoryInfo, inputParams, ref results); // sort by name for deterministic order Array.Sort(subdirs, new DirectoryInfoComparer()); foreach (DirectoryInfo subdir in subdirs) { // write information about each subdirectory to stream byte[] bytes = ASCIIEncoding.UTF8.GetBytes(subdir.Name); memoryStream.Write(bytes, 0, bytes.Length); // recurse BuildCRCStream(Path.Combine(directory, subdir.Name), inputParams, ref memoryStream); } }
private void DatabaseUpdated(DatabaseInfo info, SyncResults result, string error) { var listItem = _items.FirstOrDefault( x => x.Info == info); if (listItem == null) return; var dispatcher = Dispatcher; dispatcher.BeginInvoke(() => listItem.IsUpdating = false); switch (result) { case SyncResults.NoChange: case SyncResults.Downloaded: dispatcher.BeginInvoke(() => UpdateItem(listItem, "updated")); break; case SyncResults.Uploaded: dispatcher.BeginInvoke(() => UpdateItem(listItem, "uploaded")); break; case SyncResults.Conflict: dispatcher.BeginInvoke(() => { UpdateItem(listItem, "uploaded"); MessageBox.Show(error, Properties.Resources.ConflictTitle, MessageBoxButton.OK); }); break; case SyncResults.Failed: var msg = string.Format( Properties.Resources.UpdateFailure, info.Details.Name, error); dispatcher.BeginInvoke(() => { listItem.UpdatedIcon = null; MessageBox.Show(msg, Properties.Resources.UpdateTitle, MessageBoxButton.OK); }); break; } }
public SyncResults Synchronize(HarpFile mapFile) { var results = new SyncResults(); var isValid = sql.ConfigureAndTest(mapFile.Config.SqlConnectionString); if (!isValid) { results.Code = SynchronizeResultCode.InvalidSqlConnectionString; return(results); } try { foreach (var entry in mapFile.Entities) { int?tableId; var entity = entry.Value; // Table if (string.IsNullOrWhiteSpace(entity.Table)) { // Try match to an existing table var tables = sql.GetAllTables(); var matches = tables.Where( t => StringMatcher.IsAFuzzyMatch( getObjectName(t.fullName), entity.Name )); if (matches.Count() > 1) { // Error: too many matches, can't decide. results.Code = SynchronizeResultCode.EntityNameMatchesTooManyTables; return(results); } else if (matches.Count() == 0) { // Error: no matches results.Code = SynchronizeResultCode.EntityNameMatchesNoTables; return(results); } var tableMatch = matches.Single(); // Capture table name on entity entity.Table = tableMatch.fullName; results.WasUpdated = true; trace.AppendLine($"Table match: {entity.Table}"); } tableId = sql.GetTableObjectId(entity.Table); if (tableId == null) { results.Code = SynchronizeResultCode.MatchedTableDoesNotExist; return(results); } var columnsForEntity = sql.GetColumnNames(tableId.Value); // Columns, unmapped if (entity.Properties.Any(p => string.IsNullOrWhiteSpace(p.Value))) { for (int x = 0; x < entity.Properties.Count; x++) { var propEntry = entity.Properties.ElementAt(x); // Excludes any already mapped if (propEntry.Value != null) { continue; } var prop = propEntry.Value; var availableMatches = columnsForEntity.Where(c => !entity.Properties.Any(p => p.Value == c)); var matches = availableMatches.Where(c => StringMatcher.IsAFuzzyMatch(c, propEntry.Key)); if (matches.Any()) { var match = matches.First(); entity.Properties[propEntry.Key] = match; results.WasUpdated = true; trace.AppendLine($"Property match: {propEntry.Key} = {match}"); } else { // Error: No matches for column results.Code = SynchronizeResultCode.ColumnMatchingError; return(results); } } // Track the unmapped var unmappedCols = columnsForEntity.Except(entity.Properties.Select(p => p.Value)); results.UnmappedTableColumns.AddRange(unmappedCols); } else if (entity.Properties.Count() == 0) { // Autopopulate property list with all available columns foreach (var column in columnsForEntity) { var humanName = column.Humanize(); entity.AddProperty(humanName, column); results.WasUpdated = true; trace.AppendLine($"Property match: {humanName} = {column}"); } } // Columns, mapped var allExistingMappedColumnsExist = entity.Properties.All(p => columnsForEntity.Any(c => string.Equals(c, p.Value, StringComparison.OrdinalIgnoreCase))); if (!allExistingMappedColumnsExist) { results.Code = SynchronizeResultCode.MatchedColumnDoesNotExist; return(results); } var procsForEntity = sql.GetStoredProcsThatRefEntity(entity.Table); // Behaviours, unmapped if (entity.Behaviors.Any(b => string.IsNullOrWhiteSpace(b.Value))) { for (var x = 0; x < entity.Behaviors.Count; x++) { var behaveEntry = entity.Behaviors.ElementAt(x); // ignore already mapped behaviors if (behaveEntry.Value != null) { continue; } var behavior = behaveEntry.Value; // Excludes any already mapped var availableMatches = procsForEntity.Where(p => !entity.Behaviors.Any(b => b.Value == p.fullName)); var matches = availableMatches.Select(p => new ProcName(p.fullName)) .OrderByDescending(p => { // Remove entity name from proc's name, to make it more likely to get matched // since there's less character changes required for a total match. // e.g. 1 = get dogs by id = get by id (becomes the most closest match) // 2 = get cats by id = get cats by id var processed = p.HumanizedName.Replace((" " + entity.Name + " "), string.Empty); return(stringCompareScore(behaveEntry.Key, processed)); }).ToArray(); if (matches.Any()) { var match = matches.First().FullName; entity.Behaviors[behaveEntry.Key] = match; results.WasUpdated = true; trace.AppendLine($"Behavior match: {behaveEntry.Key} = {match}"); } } // Track the unmapped var unmappedCols = procsForEntity.Select(p => p.fullName) .Except(entity.Behaviors.Select(b => b.Value)); results.UnmappedStoredProcs.AddRange(unmappedCols); // Ensure all stored procs that have been matched // (either from this process or manually) exist. var allExistingMappedProcsExist = entity.Behaviors.All(b => procsForEntity.Any(pr => string.Equals(pr.fullName, b.Value, StringComparison.OrdinalIgnoreCase))); if (!allExistingMappedProcsExist) { results.Code = SynchronizeResultCode.MatchedProcDoesNotExist; return(results); } } else if (entity.Behaviors.Count() == 0) { // Autopopulate behavior list with all available procs foreach (var proc in procsForEntity) { var humanName = removeWord(entity.Name, getObjectName(proc.fullName).Humanize()).Humanize(); if (string.IsNullOrWhiteSpace(humanName)) { results.Code = SynchronizeResultCode.ProcMatchingError; return(results); } entity.AddBehavior(humanName, proc.fullName); results.WasUpdated = true; trace.AppendLine($"Behavior match: {humanName} = {proc}"); } } } // Validate foreach (var entity in mapFile.Entities.Select(e => e.Value)) { if (!entity.Behaviors.Any()) { results.Code = SynchronizeResultCode.AtLeastOneBehaviorRequired; return(results); } } results.Code = mapFile.Entities.All(e => e.Value.IsFullyMapped) ? SynchronizeResultCode.OK : SynchronizeResultCode.NotAllMapped; } catch (Exception ex) { trace.AppendLine($"{ex.GetType().Name}: {ex.ToString()}"); results.Code = SynchronizeResultCode.UnknownError; } return(results); }
public static CSyncReturn Syncronize(Type T) { CScriptConstSyncAttribute[] attributes = (CScriptConstSyncAttribute[])T.GetCustomAttributes(typeof(CScriptConstSyncAttribute), false); string FullPath = attributes[0].FullPathOfJavaScript; if (FullPath.StartsWith("/")) { FullPath = HttpContext.Current.Server.MapPath(FullPath); } if (!File.Exists(FullPath)) { throw new Exception(string.Format("{0} is not exists.", FullPath)); } string JsGenerated = ConvertToJavaScript(T, 0); string FileContent = CFile.GetTextInFile(FullPath); string LogDateTime = DateTime.Now.ToString(CConst.Format_yyyyMMddHHmmss); string FullPathBackup = GetFullPathBackup(FullPath, LogDateTime); string Declare = "var " + T.Name + " "; SyncResults SyncResult = SyncResults.NoChange; string JsAlready = GetJavaScriptByDeclare(FileContent, Declare); if (!string.IsNullOrEmpty(JsAlready)) { if (CFindRep.TrimWhiteSpace(JsGenerated) != CFindRep.TrimWhiteSpace(JsAlready)) { SyncResult = SyncResults.Update; } } else { SyncResult = SyncResults.Append; } if (SyncResult != SyncResults.NoChange) { string Line = string.Format("//{0} {1} by CScriptConstSyncAttribute", LogDateTime, SyncResult); string FileContentNew = ""; if (SyncResult == SyncResults.Update) { FileContentNew = FileContent.Replace(JsAlready, Line + "\r\n" + JsGenerated); } else if (SyncResult == SyncResults.Append) { FileContentNew = FileContent + "\r\n" + Line + "\r\n" + JsGenerated; } CFile.WriteTextToFile(FullPath, FileContentNew); CFile.WriteTextToFile(FullPathBackup, FileContent); } return(new CSyncReturn() { SyncResult = SyncResult, FullPathOfJavaScript = FullPath, FullPathOfJavaScriptBackup = FullPathBackup }); }
void RecieveResults(SyncResults syncResults) { //Convering values back Results results; results.rotation = Quaternion.Euler((float)syncResults.pitch / 182, (float)syncResults.yaw / 182, 0); results.position = syncResults.position; results.sprinting = syncResults.sprinting; results.crouching = syncResults.crouching; results.timeStamp = syncResults.timeStamp; //Discard out of order results if (results.timeStamp <= _lastTimeStamp) { return; } _lastTimeStamp = results.timeStamp; //Non-owner client if (!isLocalPlayer && !hasAuthority) { //Adding results to the results list so they can be used in interpolation process results.timeStamp = Time.time; _resultsList.Add(results); } //Owner client //Server client reconciliation process should be executed in order to client's rotation and position with server values but do it without jittering if (isLocalPlayer && !hasAuthority) { //Update client's position and rotation with ones from server _results.rotation = results.rotation; _results.position = results.position; int foundIndex = -1; //Search recieved time stamp in client's inputs list for (int index = 0; index < _inputsList.Count; index++) { //If time stamp found run through all inputs starting from needed time stamp if (_inputsList[index].timeStamp > results.timeStamp) { foundIndex = index; break; } } if (foundIndex == -1) { //Clear Inputs list if no needed records found while (_inputsList.Count != 0) { _inputsList.RemoveAt(0); } return; } //Replay recorded inputs for (int subIndex = foundIndex; subIndex < _inputsList.Count; subIndex++) { _results.rotation = Rotate(_inputsList[subIndex], _results); _results.crouching = Crouch(_inputsList[subIndex], _results); _results.sprinting = Sprint(_inputsList[subIndex], _results); _results.position = Move(_inputsList[subIndex], _results); } //Remove all inputs before time stamp int targetCount = _inputsList.Count - foundIndex; while (_inputsList.Count > targetCount) { _inputsList.RemoveAt(0); } } }
void FixedUpdate() { if (isLocalPlayer) { _inputs.timeStamp = Time.time; //Client side prediction for non-authoritative client or plane movement and rotation for listen server/host Vector3 lastPosition = _results.position; Quaternion lastRotation = _results.rotation; bool lastCrouch = _results.crouching; _results.rotation = Rotate(_inputs, _results); _results.crouching = Crouch(_inputs, _results); _results.sprinting = Sprint(_inputs, _results); _results.position = Move(_inputs, _results); if (hasAuthority) { //Listen server/host part //Sending results to other clients(state sync) if (_dataStep >= GetNetworkSendInterval()) { if (Vector3.Distance(_results.position, lastPosition) > 0 || Quaternion.Angle(_results.rotation, lastRotation) > 0 || _results.crouching != lastCrouch) { _results.timeStamp = _inputs.timeStamp; //Struct need to be fully new to count as dirty //Convering some of the values to get less traffic SyncResults tempResults; tempResults.yaw = (ushort)(_results.rotation.eulerAngles.y * 182); tempResults.pitch = (ushort)(_results.rotation.eulerAngles.x * 182); tempResults.position = _results.position; tempResults.sprinting = _results.sprinting; tempResults.crouching = _results.crouching; tempResults.timeStamp = _results.timeStamp; syncResults = tempResults; } _dataStep = 0; } _dataStep += Time.fixedDeltaTime; } else { //Owner client. Non-authoritative part //Add inputs to the inputs list so they could be used during reconciliation process if (Vector3.Distance(_results.position, lastPosition) > 0 || Quaternion.Angle(_results.rotation, lastRotation) > 0 || _results.crouching != lastCrouch) { _inputsList.Add(_inputs); } //Sending inputs to the server //Unfortunately there is now method overload for [Command] so I need to write several almost similar functions //This one is needed to save on network traffic SyncInputs syncInputs; syncInputs.forward = (sbyte)(_inputs.forward * 127); syncInputs.sides = (sbyte)(_inputs.sides * 127); syncInputs.vertical = (sbyte)(_inputs.vertical * 127); if (Vector3.Distance(_results.position, lastPosition) > 0) { if (Quaternion.Angle(_results.rotation, lastRotation) > 0) { Cmd_MovementRotationInputs(syncInputs.forward, syncInputs.sides, syncInputs.vertical, _inputs.pitch, _inputs.yaw, _inputs.sprint, _inputs.crouch, _inputs.timeStamp); } else { Cmd_MovementInputs(syncInputs.forward, syncInputs.sides, syncInputs.vertical, _inputs.sprint, _inputs.crouch, _inputs.timeStamp); } } else { if (Quaternion.Angle(_results.rotation, lastRotation) > 0) { Cmd_RotationInputs(_inputs.pitch, _inputs.yaw, _inputs.crouch, _inputs.timeStamp); } else { Cmd_OnlyStances(_inputs.crouch, _inputs.timeStamp); } } } } else { if (hasAuthority) { //Server //Check if there is atleast one record in inputs list if (_inputsList.Count == 0) { return; } //Move and rotate part. Nothing interesting here Inputs inputs = _inputsList[0]; _inputsList.RemoveAt(0); Vector3 lastPosition = _results.position; Quaternion lastRotation = _results.rotation; bool lastCrouch = _results.crouching; _results.rotation = Rotate(inputs, _results); _results.crouching = Crouch(inputs, _results); _results.sprinting = Sprint(inputs, _results); _results.position = Move(inputs, _results); //Sending results to other clients(state sync) if (_dataStep >= GetNetworkSendInterval()) { if (Vector3.Distance(_results.position, lastPosition) > 0 || Quaternion.Angle(_results.rotation, lastRotation) > 0 || _results.crouching != lastCrouch) { //Struct need to be fully new to count as dirty //Convering some of the values to get less traffic _results.timeStamp = inputs.timeStamp; SyncResults tempResults; tempResults.yaw = (ushort)(_results.rotation.eulerAngles.y * 182); tempResults.pitch = (ushort)(_results.rotation.eulerAngles.x * 182); tempResults.position = _results.position; tempResults.sprinting = _results.sprinting; tempResults.crouching = _results.crouching; tempResults.timeStamp = _results.timeStamp; syncResults = tempResults; } _dataStep = 0; } _dataStep += Time.fixedDeltaTime; } else { //Non-owner client a.k.a. dummy client //there should be at least two records in the results list so it would be possible to interpolate between them in case if there would be some dropped packed or latency spike //And yes this stupid structure should be here because it should start playing data when there are at least two records and continue playing even if there is only one record left if (_resultsList.Count == 0) { _playData = false; } if (_resultsList.Count >= 2) { _playData = true; } if (_playData) { if (_dataStep == 0) { _startPosition = _results.position; _startRotation = _results.rotation; } _step = 1 / (GetNetworkSendInterval()); _results.rotation = Quaternion.Slerp(_startRotation, _resultsList[0].rotation, _dataStep); _results.position = Vector3.Lerp(_startPosition, _resultsList[0].position, _dataStep); _results.crouching = _resultsList[0].crouching; _results.sprinting = _resultsList[0].sprinting; _dataStep += _step * Time.fixedDeltaTime; if (_dataStep >= 1) { _dataStep = 0; _resultsList.RemoveAt(0); } } UpdateRotation(_results.rotation); UpdatePosition(_results.position); UpdateCrouch(_results.crouching); UpdateSprinting(_results.sprinting); } } }
/// <summary> /// Runs a test case /// </summary> private static void TestOneCase(string[] srcDirectories, string[] srcFiles, string[] destDirectories, string[] destFiles, InputParams inputParams, SyncResults expectedResults) { // delete base directories in case they were hanging around from a previous failed test DeleteTestDirectory(baseDirSrc); DeleteTestDirectory(baseDirDest); // create source directories and files specified by test CreateTestDirectories(baseDirSrc, srcDirectories); CreateTestFiles(baseDirSrc, null, srcFiles); // create destination directories and files specified by test if (destDirectories != null) { CreateTestDirectories(baseDirDest, destDirectories); } if (destFiles != null) { CreateTestFiles(baseDirDest, baseDirSrc, destFiles); } // perform the directory sync SyncResults results = new SyncResults(); results = new Sync(baseDirSrc, baseDirDest).Start(inputParams); // Assert we have expected results Assert.IsTrue(SyncTools.CompareTo(expectedResults, results)); // If we are deleting extra files from destination, verify we have exactly the same files as filtered source files if (inputParams.DeleteFromDest && (!(inputParams.DeleteExcludeFiles != null) && !(inputParams.DeleteExcludeDirs != null))) { // calc hash of filtered files & directories in source tree byte[] hashSrc = CalcHash(baseDirSrc, inputParams); // calc hash of all files & directories in destination tree byte[] hashDest = CalcHash(baseDirDest, null); // hashes must match bool hashesMatch = SyncTools.CompareByteArrays(hashSrc, hashDest); Assert.IsTrue(hashesMatch); } DeleteTestDirectory(baseDirSrc); DeleteTestDirectory(baseDirDest); }
public void Initialization() { inputParams = new InputParams(); expectedResults = new SyncResults(); }
void FixedUpdate(){ if (isLocalPlayer) { _inputs.timeStamp = Time.time; //Client side prediction for non-authoritative client or plane movement and rotation for listen server/host Vector3 lastPosition = _results.position; Quaternion lastRotation = _results.rotation; bool lastCrouch = _results.crouching; _results.rotation = Rotate(_inputs,_results); _results.crouching = Crouch(_inputs,_results); _results.sprinting = Sprint(_inputs,_results); _results.position = Move(_inputs,_results); if(hasAuthority){ //Listen server/host part //Sending results to other clients(state sync) if(_dataStep >= GetNetworkSendInterval()){ if(Vector3.Distance(_results.position,lastPosition) > 0 || Quaternion.Angle(_results.rotation,lastRotation) > 0 || _results.crouching != lastCrouch ){ _results.timeStamp = _inputs.timeStamp; //Struct need to be fully new to count as dirty //Convering some of the values to get less traffic SyncResults tempResults; tempResults.yaw = (ushort)(_results.rotation.eulerAngles.y * 182); tempResults.pitch = (ushort)(_results.rotation.eulerAngles.x * 182); tempResults.position = _results.position; tempResults.sprinting = _results.sprinting; tempResults.crouching = _results.crouching; tempResults.timeStamp = _results.timeStamp; syncResults = tempResults; } _dataStep = 0; } _dataStep += Time.fixedDeltaTime; }else{ //Owner client. Non-authoritative part //Add inputs to the inputs list so they could be used during reconciliation process if(Vector3.Distance(_results.position,lastPosition) > 0 || Quaternion.Angle(_results.rotation,lastRotation) > 0 || _results.crouching != lastCrouch ){ _inputsList.Add(_inputs); } //Sending inputs to the server //Unfortunately there is now method overload for [Command] so I need to write several almost similar functions //This one is needed to save on network traffic SyncInputs syncInputs; syncInputs.forward = (sbyte)(_inputs.forward * 127); syncInputs.sides = (sbyte)(_inputs.sides * 127); syncInputs.vertical = (sbyte)(_inputs.vertical * 127); if(Vector3.Distance(_results.position,lastPosition) > 0 ){ if(Quaternion.Angle(_results.rotation,lastRotation) > 0){ Cmd_MovementRotationInputs(syncInputs.forward,syncInputs.sides,syncInputs.vertical,_inputs.pitch,_inputs.yaw,_inputs.sprint,_inputs.crouch,_inputs.timeStamp); }else{ Cmd_MovementInputs(syncInputs.forward,syncInputs.sides,syncInputs.vertical,_inputs.sprint,_inputs.crouch,_inputs.timeStamp); } }else{ if(Quaternion.Angle(_results.rotation,lastRotation) > 0){ Cmd_RotationInputs(_inputs.pitch,_inputs.yaw,_inputs.crouch,_inputs.timeStamp); }else{ Cmd_OnlyStances(_inputs.crouch,_inputs.timeStamp); } } } } else { if(hasAuthority){ //Server //Check if there is atleast one record in inputs list if(_inputsList.Count == 0){ return; } //Move and rotate part. Nothing interesting here Inputs inputs = _inputsList[0]; _inputsList.RemoveAt(0); Vector3 lastPosition = _results.position; Quaternion lastRotation = _results.rotation; bool lastCrouch = _results.crouching; _results.rotation = Rotate(inputs,_results); _results.crouching = Crouch(inputs,_results); _results.sprinting = Sprint(inputs,_results); _results.position = Move(inputs,_results); //Sending results to other clients(state sync) if(_dataStep >= GetNetworkSendInterval()){ if(Vector3.Distance(_results.position,lastPosition) > 0 || Quaternion.Angle(_results.rotation,lastRotation) > 0 || _results.crouching != lastCrouch){ //Struct need to be fully new to count as dirty //Convering some of the values to get less traffic _results.timeStamp = inputs.timeStamp; SyncResults tempResults; tempResults.yaw = (ushort)(_results.rotation.eulerAngles.y * 182); tempResults.pitch = (ushort)(_results.rotation.eulerAngles.x * 182); tempResults.position = _results.position; tempResults.sprinting = _results.sprinting; tempResults.crouching = _results.crouching; tempResults.timeStamp = _results.timeStamp; syncResults = tempResults; } _dataStep = 0; } _dataStep += Time.fixedDeltaTime; }else{ //Non-owner client a.k.a. dummy client //there should be at least two records in the results list so it would be possible to interpolate between them in case if there would be some dropped packed or latency spike //And yes this stupid structure should be here because it should start playing data when there are at least two records and continue playing even if there is only one record left if(_resultsList.Count == 0){ _playData = false; } if(_resultsList.Count >=2){ _playData = true; } if(_playData){ if(_dataStep==0){ _startPosition = _results.position; _startRotation = _results.rotation; } _step = 1/(GetNetworkSendInterval()) ; _results.rotation = Quaternion.Slerp(_startRotation,_resultsList[0].rotation,_dataStep); _results.position = Vector3.Lerp(_startPosition,_resultsList[0].position,_dataStep); _results.crouching = _resultsList[0].crouching; _results.sprinting = _resultsList[0].sprinting; _dataStep += _step * Time.fixedDeltaTime; if(_dataStep>= 1){ _dataStep = 0; _resultsList.RemoveAt(0); } } UpdateRotation(_results.rotation); UpdatePosition(_results.position); UpdateCrouch(_results.crouching ); UpdateSprinting(_results.sprinting ); } } }
void RecieveResults(SyncResults syncResults){ //Convering values back Results results; results.rotation = Quaternion.Euler ((float)syncResults.pitch/182,(float)syncResults.yaw/182,0); results.position = syncResults.position; results.sprinting = syncResults.sprinting; results.crouching = syncResults.crouching; results.timeStamp = syncResults.timeStamp; //Discard out of order results if (results.timeStamp <= _lastTimeStamp) { return; } _lastTimeStamp = results.timeStamp; //Non-owner client if (!isLocalPlayer && !hasAuthority) { //Adding results to the results list so they can be used in interpolation process results.timeStamp = Time.time; _resultsList.Add(results); } //Owner client //Server client reconciliation process should be executed in order to client's rotation and position with server values but do it without jittering if (isLocalPlayer && !hasAuthority) { //Update client's position and rotation with ones from server _results.rotation = results.rotation; _results.position = results.position; int foundIndex = -1; //Search recieved time stamp in client's inputs list for(int index = 0; index < _inputsList.Count; index++){ //If time stamp found run through all inputs starting from needed time stamp if(_inputsList[index].timeStamp > results.timeStamp){ foundIndex = index; break; } } if(foundIndex ==-1){ //Clear Inputs list if no needed records found while(_inputsList.Count != 0){ _inputsList.RemoveAt(0); } return; } //Replay recorded inputs for(int subIndex = foundIndex; subIndex < _inputsList.Count;subIndex++){ _results.rotation = Rotate(_inputsList[subIndex],_results); _results.crouching = Crouch(_inputsList[subIndex],_results); _results.sprinting = Sprint(_inputsList[subIndex],_results); _results.position = Move(_inputsList[subIndex],_results); } //Remove all inputs before time stamp int targetCount = _inputsList.Count - foundIndex; while(_inputsList.Count > targetCount){ _inputsList.RemoveAt(0); } } }