using System; using FishNet.Serializing; using GameKit.Dependencies.Utilities; namespace FishNet.Managing.Timing { public readonly struct PreciseTick : IEquatable { /// /// The current tick. /// public readonly uint Tick; /// /// Percentage of the tick returned between 0d and 1d. /// public readonly double PercentAsDouble; /// /// Percentage of the tick returned between 0 and 100. /// public readonly byte PercentAsByte; /// /// Maximum value a percent can be as a double. /// public const double MAXIMUM_DOUBLE_PERCENT = 1d; /// /// Maximum value a percent can be as a byte. /// public const byte MAXIMUM_BYTE_PERCENT = 100; /// /// Value to use when a precise tick is unset. /// public static PreciseTick GetUnsetValue() => new(TimeManager.UNSET_TICK, (byte)0); /// /// Creates a precise tick where the percentage is 0. /// public PreciseTick(uint tick) { Tick = tick; PercentAsByte = 0; PercentAsDouble = 0d; } /// /// Creates a precise tick where the percentage is a byte between 0 and 100. /// public PreciseTick(uint tick, byte percentAsByte) { Tick = tick; percentAsByte = Maths.ClampByte(percentAsByte, 0, MAXIMUM_BYTE_PERCENT); PercentAsByte = percentAsByte; PercentAsDouble = (percentAsByte / 100d); } /// /// Creates a precise tick where the percentage is a double between 0d and 1d. /// public PreciseTick(uint tick, double percent) { Tick = tick; percent = Maths.ClampDouble(percent, 0d, MAXIMUM_DOUBLE_PERCENT); PercentAsByte = (byte)(percent * 100d); PercentAsDouble = percent; } public bool IsValid() => (Tick != TimeManager.UNSET_TICK); /// /// Prints PreciseTick information as a string. /// /// public override string ToString() => $"Tick {Tick}, Percent {PercentAsByte.ToString("000")}"; public static bool operator ==(PreciseTick a, PreciseTick b) { return (a.Tick == b.Tick && a.PercentAsByte == b.PercentAsByte); } public static bool operator !=(PreciseTick a, PreciseTick b) { return !(a == b); } public static bool operator >=(PreciseTick a, PreciseTick b) { if (b.Tick > a.Tick) return false; if (a.Tick > b.Tick) return true; //If here ticks are the same. return a.PercentAsByte >= b.PercentAsByte; } public static bool operator <=(PreciseTick a, PreciseTick b) => (b >= a); public static bool operator >(PreciseTick a, PreciseTick b) { if (b.Tick > a.Tick) return false; if (a.Tick > b.Tick) return true; //if here ticks are the same. return a.PercentAsByte > b.PercentAsByte; } public static bool operator <(PreciseTick a, PreciseTick b) => (b > a); public bool Equals(PreciseTick other) => (Tick == other.Tick && PercentAsByte == other.PercentAsByte); public override bool Equals(object obj) => obj is PreciseTick other && Equals(other); public override int GetHashCode() => HashCode.Combine(Tick, PercentAsDouble, PercentAsByte); } public static class PreciseTickExtensions { /// /// Adds value onto a PreciseTick. /// /// Value to add. /// Tick delta. /// public static PreciseTick Add(this PreciseTick pt, PreciseTick value, double delta) { double ptDouble = pt.AsDouble(delta); double valueDouble = value.AsDouble(delta); double next = (ptDouble + valueDouble); return next.AsPreciseTick(delta); } /// /// Subtracts value from a PreciseTick. /// /// Value to subtract. /// Tick delta. /// public static PreciseTick Subtract(this PreciseTick pt, PreciseTick value, double delta) { double ptDouble = pt.AsDouble(delta); double valueDouble = value.AsDouble(delta); double remainder = (ptDouble - valueDouble); return remainder.AsPreciseTick(delta); } /// /// Converts a PreciceTick to a double. /// /// Tick delta. /// public static double AsDouble(this PreciseTick pt, double delta) { return ((double)pt.Tick * delta) + (pt.PercentAsDouble * delta); } /// /// Converts a double to a PreciseTick. /// /// Tick delta. /// public static PreciseTick AsPreciseTick(this double ptDouble, double delta) { if (ptDouble <= 0) return new(0, 0); ulong whole = (ulong)Math.Floor(ptDouble / delta); //Overflow. if (whole >= uint.MaxValue) return PreciseTick.GetUnsetValue(); double remainder = (ptDouble % delta); double percent = (remainder / delta); return new((uint)whole, percent); } } public static class PreciseTickSerializer { public static void WritePreciseTick(this Writer writer, PreciseTick value) { writer.WriteTickUnpacked(value.Tick); writer.WriteUInt8Unpacked(value.PercentAsByte); } public static PreciseTick ReadPreciseTick(this Reader reader) { uint tick = reader.ReadTickUnpacked(); byte percentByte = reader.ReadUInt8Unpacked(); return new(tick, percentByte); } } }