From 7df45ebef04e91a1f913fe9954767f015f44dbc7 Mon Sep 17 00:00:00 2001 From: tychon Date: Wed, 30 Dec 2020 22:12:50 -0500 Subject: [PATCH] Add Netcode as source library --- Netcode/AbstractNetEvent1.cs | 134 ++++ Netcode/AbstractNetSerializable.cs | 285 +++++++ Netcode/BinaryReaderWriterExtensions.cs | 186 +++++ Netcode/ILoggingWriter.cs | 9 + Netcode/INetObject.cs | 10 + Netcode/INetRoot.cs | 14 + Netcode/INetSerializable.cs | 55 ++ Netcode/InterpolationCancellable.cs | 7 + Netcode/NetArray.cs | 313 ++++++++ Netcode/NetBool.cs | 43 + Netcode/NetByte.cs | 43 + Netcode/NetClock.cs | 75 ++ Netcode/NetCollection.cs | 274 +++++++ Netcode/NetColor.cs | 103 +++ Netcode/NetDictionary.cs | 957 +++++++++++++++++++++++ Netcode/NetDouble.cs | 48 ++ Netcode/NetEnum.cs | 75 ++ Netcode/NetEvent0.cs | 71 ++ Netcode/NetEvent1.cs | 19 + Netcode/NetEvent1Field.cs | 21 + Netcode/NetEventArg.cs | 11 + Netcode/NetEventBinary.cs | 52 ++ Netcode/NetExtendableRef.cs | 77 ++ Netcode/NetField.cs | 41 + Netcode/NetFieldBase.cs | 366 +++++++++ Netcode/NetFieldDictionary.cs | 31 + Netcode/NetFields.cs | 131 ++++ Netcode/NetFloat.cs | 48 ++ Netcode/NetGuid.cs | 44 ++ Netcode/NetGuidDictionary.cs | 28 + Netcode/NetInt.cs | 58 ++ Netcode/NetIntDelta.cs | 85 ++ Netcode/NetIntList.cs | 49 ++ Netcode/NetList.cs | 432 ++++++++++ Netcode/NetLong.cs | 53 ++ Netcode/NetLongList.cs | 49 ++ Netcode/NetNullableEnum.cs | 98 +++ Netcode/NetObjectArray.cs | 21 + Netcode/NetObjectList.cs | 21 + Netcode/NetObjectShrinkList.cs | 388 +++++++++ Netcode/NetPoint.cs | 105 +++ Netcode/NetRectangle.cs | 157 ++++ Netcode/NetRef.cs | 14 + Netcode/NetRefBase.cs | 244 ++++++ Netcode/NetRefTypes.cs | 104 +++ Netcode/NetRoot.cs | 134 ++++ Netcode/NetRootDictionary.cs | 206 +++++ Netcode/NetRotation.cs | 62 ++ Netcode/NetString.cs | 71 ++ Netcode/NetStringList.cs | 21 + Netcode/NetTimestamp.cs | 28 + Netcode/NetVector2.cs | 256 ++++++ Netcode/NetVersion.cs | 194 +++++ Netcode/Netcode.csproj | 33 + Netcode/Properties/AssemblyInfo.cs | 20 + Netcode/SerializationCollectionFacade.cs | 31 + Netcode/SerializationFacade.cs | 32 + Netcode/StardewValley.Util/GuidHelper.cs | 12 + StardewValley.sln | 6 + StardewValley/StardewValley.csproj | 4 +- 60 files changed, 6556 insertions(+), 3 deletions(-) create mode 100644 Netcode/AbstractNetEvent1.cs create mode 100644 Netcode/AbstractNetSerializable.cs create mode 100644 Netcode/BinaryReaderWriterExtensions.cs create mode 100644 Netcode/ILoggingWriter.cs create mode 100644 Netcode/INetObject.cs create mode 100644 Netcode/INetRoot.cs create mode 100644 Netcode/INetSerializable.cs create mode 100644 Netcode/InterpolationCancellable.cs create mode 100644 Netcode/NetArray.cs create mode 100644 Netcode/NetBool.cs create mode 100644 Netcode/NetByte.cs create mode 100644 Netcode/NetClock.cs create mode 100644 Netcode/NetCollection.cs create mode 100644 Netcode/NetColor.cs create mode 100644 Netcode/NetDictionary.cs create mode 100644 Netcode/NetDouble.cs create mode 100644 Netcode/NetEnum.cs create mode 100644 Netcode/NetEvent0.cs create mode 100644 Netcode/NetEvent1.cs create mode 100644 Netcode/NetEvent1Field.cs create mode 100644 Netcode/NetEventArg.cs create mode 100644 Netcode/NetEventBinary.cs create mode 100644 Netcode/NetExtendableRef.cs create mode 100644 Netcode/NetField.cs create mode 100644 Netcode/NetFieldBase.cs create mode 100644 Netcode/NetFieldDictionary.cs create mode 100644 Netcode/NetFields.cs create mode 100644 Netcode/NetFloat.cs create mode 100644 Netcode/NetGuid.cs create mode 100644 Netcode/NetGuidDictionary.cs create mode 100644 Netcode/NetInt.cs create mode 100644 Netcode/NetIntDelta.cs create mode 100644 Netcode/NetIntList.cs create mode 100644 Netcode/NetList.cs create mode 100644 Netcode/NetLong.cs create mode 100644 Netcode/NetLongList.cs create mode 100644 Netcode/NetNullableEnum.cs create mode 100644 Netcode/NetObjectArray.cs create mode 100644 Netcode/NetObjectList.cs create mode 100644 Netcode/NetObjectShrinkList.cs create mode 100644 Netcode/NetPoint.cs create mode 100644 Netcode/NetRectangle.cs create mode 100644 Netcode/NetRef.cs create mode 100644 Netcode/NetRefBase.cs create mode 100644 Netcode/NetRefTypes.cs create mode 100644 Netcode/NetRoot.cs create mode 100644 Netcode/NetRootDictionary.cs create mode 100644 Netcode/NetRotation.cs create mode 100644 Netcode/NetString.cs create mode 100644 Netcode/NetStringList.cs create mode 100644 Netcode/NetTimestamp.cs create mode 100644 Netcode/NetVector2.cs create mode 100644 Netcode/NetVersion.cs create mode 100644 Netcode/Netcode.csproj create mode 100644 Netcode/Properties/AssemblyInfo.cs create mode 100644 Netcode/SerializationCollectionFacade.cs create mode 100644 Netcode/SerializationFacade.cs create mode 100644 Netcode/StardewValley.Util/GuidHelper.cs diff --git a/Netcode/AbstractNetEvent1.cs b/Netcode/AbstractNetEvent1.cs new file mode 100644 index 0000000..8988c03 --- /dev/null +++ b/Netcode/AbstractNetEvent1.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Netcode +{ + public abstract class AbstractNetEvent1 : AbstractNetSerializable + { + private class EventRecording + { + public T arg; + + public uint timestamp; + + public EventRecording(T arg, uint timestamp) + { + this.arg = arg; + this.timestamp = timestamp; + } + } + + public delegate void Event(T arg); + + public bool InterpolationWait = true; + + private List outgoingEvents = new List(); + + private List incomingEvents = new List(); + + public event Event onEvent; + + public AbstractNetEvent1() + { + } + + public bool HasPendingEvent(Predicate match) + { + return incomingEvents.Exists((EventRecording e) => match(e.arg)); + } + + public void Clear() + { + outgoingEvents.Clear(); + incomingEvents.Clear(); + } + + public void Fire(T arg) + { + EventRecording recording = new EventRecording(arg, GetLocalTick()); + outgoingEvents.Add(recording); + incomingEvents.Add(recording); + MarkDirty(); + Poll(); + } + + public void Poll() + { + List triggeredEvents = null; + foreach (EventRecording e2 in incomingEvents) + { + if (base.Root != null && GetLocalTick() < e2.timestamp) + { + break; + } + if (triggeredEvents == null) + { + triggeredEvents = new List(); + } + triggeredEvents.Add(e2); + } + if (triggeredEvents != null && triggeredEvents.Count > 0) + { + incomingEvents.RemoveAll(triggeredEvents.Contains); + if (this.onEvent != null) + { + foreach (EventRecording e in triggeredEvents) + { + this.onEvent(e.arg); + } + } + } + } + + protected abstract T readEventArg(BinaryReader reader, NetVersion version); + + protected abstract void writeEventArg(BinaryWriter writer, T arg); + + public override void Read(BinaryReader reader, NetVersion version) + { + uint count = reader.Read7BitEncoded(); + uint timestamp = GetLocalTick(); + if (InterpolationWait) + { + timestamp = (uint)((int)timestamp + base.Root.Clock.InterpolationTicks); + } + for (uint i = 0u; i < count; i++) + { + uint delay = reader.ReadUInt32(); + incomingEvents.Add(new EventRecording(readEventArg(reader, version), timestamp + delay)); + } + ChangeVersion.Merge(version); + } + + public override void ReadFull(BinaryReader reader, NetVersion version) + { + ChangeVersion.Merge(version); + } + + public override void Write(BinaryWriter writer) + { + writer.Write7BitEncoded((uint)outgoingEvents.Count); + if (outgoingEvents.Count > 0) + { + uint baseTime = outgoingEvents[0].timestamp; + foreach (EventRecording e in outgoingEvents) + { + writer.Write(e.timestamp - baseTime); + writeEventArg(writer, e.arg); + } + } + outgoingEvents.Clear(); + } + + protected override void CleanImpl() + { + base.CleanImpl(); + outgoingEvents.Clear(); + } + + public override void WriteFull(BinaryWriter writer) + { + } + } +} diff --git a/Netcode/AbstractNetSerializable.cs b/Netcode/AbstractNetSerializable.cs new file mode 100644 index 0000000..79cbd7f --- /dev/null +++ b/Netcode/AbstractNetSerializable.cs @@ -0,0 +1,285 @@ +using System; +using System.IO; + +namespace Netcode +{ + public abstract class AbstractNetSerializable : INetSerializable, INetObject + { + private uint dirtyTick = uint.MaxValue; + + private uint minNextDirtyTime; + + protected NetVersion ChangeVersion; + + public ushort DeltaAggregateTicks; + + private bool needsTick; + + private bool childNeedsTick; + + private INetSerializable parent; + + public uint DirtyTick + { + get + { + return dirtyTick; + } + set + { + if (value < dirtyTick) + { + SetDirtySooner(value); + } + else if (value > dirtyTick) + { + SetDirtyLater(value); + } + } + } + + public virtual bool Dirty => dirtyTick != uint.MaxValue; + + public bool NeedsTick + { + get + { + return needsTick; + } + set + { + if (value != needsTick) + { + needsTick = value; + if (value && Parent != null) + { + Parent.ChildNeedsTick = true; + } + } + } + } + + public bool ChildNeedsTick + { + get + { + return childNeedsTick; + } + set + { + if (value != childNeedsTick) + { + childNeedsTick = value; + if (value && Parent != null) + { + Parent.ChildNeedsTick = true; + } + } + } + } + + public INetRoot Root + { + get; + protected set; + } + + public INetSerializable Parent + { + get + { + return parent; + } + set + { + SetParent(value); + } + } + + public INetSerializable NetFields => this; + + protected void SetDirtySooner(uint tick) + { + tick = Math.Max(tick, minNextDirtyTime); + if (dirtyTick > tick) + { + dirtyTick = tick; + if (Parent != null) + { + Parent.DirtyTick = Math.Min(Parent.DirtyTick, tick); + } + if (Root != null) + { + minNextDirtyTime = Root.Clock.GetLocalTick() + DeltaAggregateTicks; + ChangeVersion.Set(Root.Clock.netVersion); + } + else + { + minNextDirtyTime = 0u; + ChangeVersion.Clear(); + } + } + } + + protected void SetDirtyLater(uint tick) + { + if (dirtyTick < tick) + { + dirtyTick = tick; + ForEachChild(delegate(INetSerializable child) + { + child.DirtyTick = Math.Max(child.DirtyTick, tick); + }); + if (tick == uint.MaxValue) + { + CleanImpl(); + } + } + } + + protected virtual void CleanImpl() + { + if (Root == null) + { + minNextDirtyTime = 0u; + } + else + { + minNextDirtyTime = Root.Clock.GetLocalTick() + DeltaAggregateTicks; + } + } + + public void MarkDirty() + { + if (Root == null) + { + SetDirtySooner(0u); + } + else + { + SetDirtySooner(Root.Clock.GetLocalTick()); + } + } + + public void MarkClean() + { + SetDirtyLater(uint.MaxValue); + } + + protected virtual bool tickImpl() + { + return false; + } + + public bool Tick() + { + if (needsTick) + { + needsTick = tickImpl(); + } + if (childNeedsTick) + { + childNeedsTick = false; + ForEachChild(delegate(INetSerializable child) + { + if (child.NeedsTick || child.ChildNeedsTick) + { + childNeedsTick |= child.Tick(); + } + }); + } + return childNeedsTick | needsTick; + } + + public abstract void Read(BinaryReader reader, NetVersion version); + + public abstract void Write(BinaryWriter writer); + + public abstract void ReadFull(BinaryReader reader, NetVersion version); + + public abstract void WriteFull(BinaryWriter writer); + + protected uint GetLocalTick() + { + if (Root != null) + { + return Root.Clock.GetLocalTick(); + } + return 0u; + } + + protected NetTimestamp GetLocalTimestamp() + { + if (Root != null) + { + return Root.Clock.GetLocalTimestamp(); + } + return default(NetTimestamp); + } + + protected NetVersion GetLocalVersion() + { + if (Root != null) + { + return new NetVersion(Root.Clock.netVersion); + } + return default(NetVersion); + } + + protected virtual void SetParent(INetSerializable parent) + { + this.parent = parent; + if (parent != null) + { + Root = parent.Root; + SetChildParents(); + } + else + { + ClearChildParents(); + } + MarkClean(); + ChangeVersion.Clear(); + minNextDirtyTime = 0u; + } + + protected virtual void SetChildParents() + { + ForEachChild(delegate(INetSerializable child) + { + child.Parent = this; + }); + } + + protected virtual void ClearChildParents() + { + ForEachChild(delegate(INetSerializable child) + { + if (child.Parent == this) + { + child.Parent = null; + } + }); + } + + protected virtual void ValidateChild(INetSerializable child) + { + if ((Parent != null || Root == this) && child.Parent != this) + { + throw new InvalidOperationException(); + } + } + + protected virtual void ValidateChildren() + { + if (Parent != null || Root == this) + { + ForEachChild(ValidateChild); + } + } + + protected virtual void ForEachChild(Action childAction) + { + } + } +} diff --git a/Netcode/BinaryReaderWriterExtensions.cs b/Netcode/BinaryReaderWriterExtensions.cs new file mode 100644 index 0000000..65a813a --- /dev/null +++ b/Netcode/BinaryReaderWriterExtensions.cs @@ -0,0 +1,186 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections; +using System.IO; + +namespace Netcode +{ + public static class BinaryReaderWriterExtensions + { + public static void ReadSkippable(this BinaryReader reader, Action readAction) + { + uint size = reader.ReadUInt32(); + long startPosition = reader.BaseStream.Position; + readAction(); + if (reader.BaseStream.Position > startPosition + size) + { + throw new InvalidOperationException(); + } + reader.BaseStream.Position = startPosition + size; + } + + public static byte[] ReadSkippableBytes(this BinaryReader reader) + { + uint dataLength = reader.ReadUInt32(); + return reader.ReadBytes((int)dataLength); + } + + public static void Skip(this BinaryReader reader) + { + reader.ReadSkippable(delegate + { + }); + } + + public static void WriteSkippable(this BinaryWriter writer, Action writeAction) + { + long sizePosition = writer.BaseStream.Position; + writer.Write(0u); + long startPosition = writer.BaseStream.Position; + writeAction(); + long endPosition = writer.BaseStream.Position; + long size = endPosition - startPosition; + writer.BaseStream.Position = sizePosition; + writer.Write((uint)size); + writer.BaseStream.Position = endPosition; + } + + public static BitArray ReadBitArray(this BinaryReader reader) + { + int length = (int)reader.Read7BitEncoded(); + return new BitArray(reader.ReadBytes((length + 7) / 8)) + { + Length = length + }; + } + + public static void WriteBitArray(this BinaryWriter writer, BitArray bits) + { + byte[] buf = new byte[(bits.Length + 7) / 8]; + bits.CopyTo(buf, 0); + writer.Write7BitEncoded((uint)bits.Length); + writer.Write(buf); + } + + public static void Write7BitEncoded(this BinaryWriter writer, uint value) + { + do + { + byte chunk = (byte)(value & 0x7F); + value >>= 7; + if (value != 0) + { + chunk = (byte)(chunk | 0x80); + } + writer.Write(chunk); + } + while (value != 0); + } + + public static uint Read7BitEncoded(this BinaryReader reader) + { + uint value = 0u; + byte chunk = reader.ReadByte(); + int shift = 0; + while ((chunk & 0x80) != 0) + { + value = (uint)((int)value | ((chunk & 0x7F) << shift)); + shift += 7; + chunk = reader.ReadByte(); + } + return (uint)((int)value | ((chunk & 0x7F) << shift)); + } + + public static Guid ReadGuid(this BinaryReader reader) + { + return new Guid(reader.ReadBytes(16)); + } + + public static void WriteGuid(this BinaryWriter writer, Guid guid) + { + writer.Write(guid.ToByteArray()); + } + + public static Vector2 ReadVector2(this BinaryReader reader) + { + float x = reader.ReadSingle(); + float y = reader.ReadSingle(); + return new Vector2(x, y); + } + + public static void WriteVector2(this BinaryWriter writer, Vector2 vec) + { + writer.Write(vec.X); + writer.Write(vec.Y); + } + + public static Point ReadPoint(this BinaryReader reader) + { + int x = reader.ReadInt32(); + int y = reader.ReadInt32(); + return new Point(x, y); + } + + public static void WritePoint(this BinaryWriter writer, Point p) + { + writer.Write(p.X); + writer.Write(p.Y); + } + + public static Rectangle ReadRectangle(this BinaryReader reader) + { + Point pos = reader.ReadPoint(); + Point size = reader.ReadPoint(); + return new Rectangle(pos.X, pos.Y, size.X, size.Y); + } + + public static void WriteRectangle(this BinaryWriter writer, Rectangle rect) + { + writer.WritePoint(rect.Location); + writer.WritePoint(new Point(rect.Width, rect.Height)); + } + + public static Color ReadColor(this BinaryReader reader) + { + Color color = default(Color); + color.PackedValue = reader.ReadUInt32(); + return color; + } + + public static void WriteColor(this BinaryWriter writer, Color color) + { + writer.Write(color.PackedValue); + } + + public static T ReadEnum(this BinaryReader reader) where T : struct, IConvertible + { + return (T)Enum.ToObject(typeof(T), reader.ReadInt16()); + } + + public static void WriteEnum(this BinaryWriter writer, T enumValue) where T : struct, IConvertible + { + writer.Write(Convert.ToInt16(enumValue)); + } + + public static void WriteEnum(this BinaryWriter writer, object enumValue) + { + writer.Write(Convert.ToInt16(enumValue)); + } + + public static void Push(this BinaryWriter writer, string name) + { + if (writer is ILoggingWriter) + { + (writer as ILoggingWriter).Push(name); + } + } + + public static void Pop(this BinaryWriter writer) + { + if (writer is ILoggingWriter) + { + (writer as ILoggingWriter).Pop(); + } + } + } +} diff --git a/Netcode/ILoggingWriter.cs b/Netcode/ILoggingWriter.cs new file mode 100644 index 0000000..ddc02e4 --- /dev/null +++ b/Netcode/ILoggingWriter.cs @@ -0,0 +1,9 @@ +namespace Netcode +{ + public interface ILoggingWriter + { + void Push(string name); + + void Pop(); + } +} diff --git a/Netcode/INetObject.cs b/Netcode/INetObject.cs new file mode 100644 index 0000000..139d309 --- /dev/null +++ b/Netcode/INetObject.cs @@ -0,0 +1,10 @@ +namespace Netcode +{ + public interface INetObject where T : INetSerializable + { + T NetFields + { + get; + } + } +} diff --git a/Netcode/INetRoot.cs b/Netcode/INetRoot.cs new file mode 100644 index 0000000..e78b20b --- /dev/null +++ b/Netcode/INetRoot.cs @@ -0,0 +1,14 @@ +namespace Netcode +{ + public interface INetRoot + { + NetClock Clock + { + get; + } + + void TickTree(); + + void Disconnect(long connection); + } +} diff --git a/Netcode/INetSerializable.cs b/Netcode/INetSerializable.cs new file mode 100644 index 0000000..4bf2d79 --- /dev/null +++ b/Netcode/INetSerializable.cs @@ -0,0 +1,55 @@ +using System.IO; + +namespace Netcode +{ + public interface INetSerializable + { + uint DirtyTick + { + get; + set; + } + + bool Dirty + { + get; + } + + bool NeedsTick + { + get; + set; + } + + bool ChildNeedsTick + { + get; + set; + } + + INetSerializable Parent + { + get; + set; + } + + INetRoot Root + { + get; + } + + void MarkDirty(); + + void MarkClean(); + + bool Tick(); + + void Read(BinaryReader reader, NetVersion version); + + void Write(BinaryWriter writer); + + void ReadFull(BinaryReader reader, NetVersion version); + + void WriteFull(BinaryWriter writer); + } +} diff --git a/Netcode/InterpolationCancellable.cs b/Netcode/InterpolationCancellable.cs new file mode 100644 index 0000000..84b422a --- /dev/null +++ b/Netcode/InterpolationCancellable.cs @@ -0,0 +1,7 @@ +namespace Netcode +{ + public interface InterpolationCancellable + { + void CancelInterpolation(); + } +} diff --git a/Netcode/NetArray.cs b/Netcode/NetArray.cs new file mode 100644 index 0000000..6412285 --- /dev/null +++ b/Netcode/NetArray.cs @@ -0,0 +1,313 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Netcode +{ + public class NetArray : AbstractNetSerializable, IList, ICollection, IEnumerable, IEnumerable, IEquatable> where TField : NetField, new() + { + public delegate void FieldCreateEvent(int index, TField field); + + private int appendPosition; + + private readonly List elements = new List(); + + public List Fields => elements; + + public T this[int index] + { + get + { + return elements[index].Get(); + } + set + { + elements[index].Set(value); + } + } + + public int Count => elements.Count; + + public int Length => elements.Count; + + public bool IsReadOnly => false; + + public bool IsFixedSize => base.Parent != null; + + public event FieldCreateEvent OnFieldCreate; + + public NetArray() + { + } + + public NetArray(IEnumerable values) + : this() + { + int i = 0; + foreach (T value in values) + { + TField field = createField(i++); + field.Set(value); + elements.Add(field); + } + } + + public NetArray(int size) + : this() + { + for (int i = 0; i < size; i++) + { + elements.Add(createField(i)); + } + } + + private TField createField(int index) + { + TField field = new TField().Interpolated(interpolate: false, wait: false); + if (this.OnFieldCreate != null) + { + this.OnFieldCreate(index, field); + } + return field; + } + + public void Add(T item) + { + if (IsFixedSize) + { + throw new InvalidOperationException(); + } + while (appendPosition >= elements.Count) + { + elements.Add(createField(elements.Count)); + } + elements[appendPosition].Set(item); + appendPosition++; + } + + public void Clear() + { + if (IsFixedSize) + { + throw new InvalidOperationException(); + } + elements.Clear(); + } + + public bool Contains(T item) + { + foreach (TField element in elements) + { + if (object.Equals(element.Get(), item)) + { + return true; + } + } + return false; + } + + public void CopyTo(T[] array, int arrayIndex) + { + if (array == null) + { + throw new ArgumentNullException(); + } + if (arrayIndex < 0) + { + throw new ArgumentOutOfRangeException(); + } + if (Count - arrayIndex > array.Length) + { + throw new ArgumentException(); + } + using (IEnumerator enumerator = GetEnumerator()) + { + while (enumerator.MoveNext()) + { + T value = enumerator.Current; + array[arrayIndex++] = value; + } + } + } + + private void ensureCapacity(int size) + { + if (IsFixedSize && size != Count) + { + throw new InvalidOperationException(); + } + while (Count < size) + { + elements.Add(createField(Count)); + } + } + + public void SetCount(int size) + { + ensureCapacity(size); + } + + public void Set(IList values) + { + ensureCapacity(values.Count); + for (int i = 0; i < Count; i++) + { + this[i] = values[i]; + } + } + + public void Set(IEnumerable values) + { + ensureCapacity(values.Count()); + int i = 0; + foreach (T value in values) + { + this[i++] = value; + } + } + + public bool Equals(NetArray other) + { + return object.Equals(elements, other.elements); + } + + public override bool Equals(object obj) + { + if (obj is NetArray) + { + return Equals(obj as NetArray); + } + return false; + } + + public override int GetHashCode() + { + return elements.GetHashCode() ^ 0x300A5A8D; + } + + public IEnumerator GetEnumerator() + { + foreach (TField elementField in elements) + { + yield return elementField.Get(); + } + } + + public int IndexOf(T item) + { + for (int i = 0; i < Count; i++) + { + if (object.Equals(elements[i].Get(), item)) + { + return i; + } + } + return -1; + } + + public void Insert(int index, T item) + { + if (IsFixedSize) + { + throw new InvalidOperationException(); + } + TField field = createField(index); + field.Set(item); + elements.Insert(index, field); + } + + public bool Remove(T item) + { + int index = IndexOf(item); + if (index != -1) + { + RemoveAt(index); + return true; + } + return false; + } + + public void RemoveAt(int index) + { + if (IsFixedSize) + { + throw new InvalidOperationException(); + } + elements.RemoveAt(index); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public override void Read(BinaryReader reader, NetVersion version) + { + BitArray dirtyBits = reader.ReadBitArray(); + for (int i = 0; i < elements.Count; i++) + { + if (dirtyBits[i]) + { + elements[i].Read(reader, version); + } + } + } + + public override void Write(BinaryWriter writer) + { + BitArray dirtyBits = new BitArray(elements.Count); + for (int j = 0; j < elements.Count; j++) + { + dirtyBits[j] = elements[j].Dirty; + } + writer.WriteBitArray(dirtyBits); + for (int i = 0; i < elements.Count; i++) + { + if (dirtyBits[i]) + { + elements[i].Write(writer); + } + } + } + + public override void ReadFull(BinaryReader reader, NetVersion version) + { + int size = reader.ReadInt32(); + elements.Clear(); + for (int i = 0; i < size; i++) + { + TField element = createField(elements.Count); + element.ReadFull(reader, version); + if (base.Parent != null) + { + element.Parent = this; + } + elements.Add(element); + } + } + + public override void WriteFull(BinaryWriter writer) + { + writer.Write(Count); + foreach (TField element in elements) + { + element.WriteFull(writer); + } + } + + protected override void ForEachChild(Action childAction) + { + foreach (TField elementField in elements) + { + childAction(elementField); + } + } + + public override string ToString() + { + return string.Join(",", this); + } + } +} diff --git a/Netcode/NetBool.cs b/Netcode/NetBool.cs new file mode 100644 index 0000000..a5b798b --- /dev/null +++ b/Netcode/NetBool.cs @@ -0,0 +1,43 @@ +using System.IO; + +namespace Netcode +{ + public sealed class NetBool : NetField + { + public NetBool() + { + } + + public NetBool(bool value) + : base(value) + { + } + + public override void Set(bool newValue) + { + if (canShortcutSet()) + { + value = newValue; + } + else if (newValue != value) + { + cleanSet(newValue); + MarkDirty(); + } + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + bool newValue = reader.ReadBoolean(); + if (version.IsPriorityOver(ChangeVersion)) + { + setInterpolationTarget(newValue); + } + } + + protected override void WriteDelta(BinaryWriter writer) + { + writer.Write(value); + } + } +} diff --git a/Netcode/NetByte.cs b/Netcode/NetByte.cs new file mode 100644 index 0000000..254766f --- /dev/null +++ b/Netcode/NetByte.cs @@ -0,0 +1,43 @@ +using System.IO; + +namespace Netcode +{ + public sealed class NetByte : NetField + { + public NetByte() + { + } + + public NetByte(byte value) + : base(value) + { + } + + public override void Set(byte newValue) + { + if (canShortcutSet()) + { + value = newValue; + } + else if (newValue != value) + { + cleanSet(newValue); + MarkDirty(); + } + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + byte newValue = reader.ReadByte(); + if (version.IsPriorityOver(ChangeVersion)) + { + setInterpolationTarget(newValue); + } + } + + protected override void WriteDelta(BinaryWriter writer) + { + writer.Write(base.Value); + } + } +} diff --git a/Netcode/NetClock.cs b/Netcode/NetClock.cs new file mode 100644 index 0000000..e291922 --- /dev/null +++ b/Netcode/NetClock.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; + +namespace Netcode +{ + public class NetClock + { + public NetVersion netVersion; + + public int LocalId; + + public int InterpolationTicks; + + public List blanks = new List(); + + public NetClock() + { + netVersion = default(NetVersion); + LocalId = AddNewPeer(); + } + + public int AddNewPeer() + { + int id = blanks.IndexOf(item: true); + if (id != -1) + { + blanks[id] = false; + } + else + { + id = netVersion.Size(); + while (blanks.Count < netVersion.Size()) + { + blanks.Add(item: false); + } + netVersion[id] = 0u; + } + return id; + } + + public void RemovePeer(int id) + { + while (blanks.Count <= id) + { + blanks.Add(item: false); + } + blanks[id] = true; + } + + public uint GetLocalTick() + { + return netVersion[LocalId]; + } + + public NetTimestamp GetLocalTimestamp() + { + return netVersion.GetTimestamp(LocalId); + } + + public void Tick() + { + uint num = ++netVersion[LocalId]; + } + + public void Clear() + { + netVersion.Clear(); + LocalId = 0; + } + + public override string ToString() + { + return base.ToString() + ";LocalId=" + LocalId; + } + } +} diff --git a/Netcode/NetCollection.cs b/Netcode/NetCollection.cs new file mode 100644 index 0000000..1ecdc8f --- /dev/null +++ b/Netcode/NetCollection.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; + +namespace Netcode +{ + public sealed class NetCollection : AbstractNetSerializable, IList, ICollection, IEnumerable, IEnumerable, IEquatable> where T : class, INetObject + { + public delegate void ContentsChangeEvent(T value); + + private List guids = new List(); + + private List list = new List(); + + private NetGuidDictionary> elements = new NetGuidDictionary>(); + + public int Count => list.Count; + + public bool IsFixedSize => false; + + public bool IsReadOnly => false; + + public bool InterpolationWait + { + get + { + return elements.InterpolationWait; + } + set + { + elements.InterpolationWait = value; + } + } + + public T this[int index] + { + get + { + return list[index]; + } + set + { + elements[guids[index]] = value; + } + } + + public T this[Guid guid] => elements[guid]; + + public event ContentsChangeEvent OnValueAdded; + + public event ContentsChangeEvent OnValueRemoved; + + public NetCollection() + { + elements.OnValueTargetUpdated += delegate(Guid guid, T old_target_value, T new_target_value) + { + if (old_target_value != new_target_value) + { + int num3 = guids.IndexOf(guid); + if (num3 == -1) + { + guids.Add(guid); + list.Add(new_target_value); + } + else + { + list[num3] = new_target_value; + } + } + }; + elements.OnValueAdded += delegate(Guid guid, T value) + { + int num2 = guids.IndexOf(guid); + if (num2 == -1) + { + guids.Add(guid); + list.Add(value); + } + else + { + list[num2] = value; + } + if (this.OnValueAdded != null) + { + this.OnValueAdded(value); + } + }; + elements.OnValueRemoved += delegate(Guid guid, T value) + { + int num = guids.IndexOf(guid); + if (num != -1) + { + guids.RemoveAt(num); + list.RemoveAt(num); + } + if (this.OnValueRemoved != null) + { + this.OnValueRemoved(value); + } + }; + } + + public NetCollection(IEnumerable values) + : this() + { + foreach (T value in values) + { + Add(value); + } + } + + public void Add(T item) + { + Guid key = Guid.NewGuid(); + elements.Add(key, item); + } + + public bool Equals(NetCollection other) + { + return elements.Equals(other.elements); + } + + public List.Enumerator GetEnumerator() + { + return list.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return list.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Clear() + { + elements.Clear(); + } + + public void Set(ICollection other) + { + Clear(); + foreach (T elem in other) + { + Add(elem); + } + } + + public bool Contains(T item) + { + return list.Contains(item); + } + + public bool ContainsGuid(Guid guid) + { + return elements.ContainsKey(guid); + } + + public Guid GuidOf(T item) + { + for (int i = 0; i < list.Count; i++) + { + if (list[i] == item) + { + return guids[i]; + } + } + return Guid.Empty; + } + + public int IndexOf(T item) + { + return list.IndexOf(item); + } + + public void Insert(int index, T item) + { + throw new NotSupportedException(); + } + + public void CopyTo(T[] array, int arrayIndex) + { + if (array == null) + { + throw new ArgumentNullException(); + } + if (arrayIndex < 0) + { + throw new ArgumentOutOfRangeException(); + } + if (Count - arrayIndex > array.Length) + { + throw new ArgumentException(); + } + using (List.Enumerator enumerator = GetEnumerator()) + { + while (enumerator.MoveNext()) + { + T value = enumerator.Current; + array[arrayIndex++] = value; + } + } + } + + public bool Remove(T item) + { + foreach (Guid key in guids) + { + if (elements[key] == item) + { + elements.Remove(key); + return true; + } + } + return false; + } + + public void RemoveAt(int index) + { + elements.Remove(guids[index]); + } + + public void Remove(Guid guid) + { + elements.Remove(guid); + } + + public void Filter(Func f) + { + int i = 0; + while (i < list.Count) + { + T v = list[i]; + if (!f(v)) + { + elements.Remove(guids[i]); + } + else + { + i++; + } + } + } + + protected override void ForEachChild(Action childAction) + { + childAction(elements); + } + + public override void Read(BinaryReader reader, NetVersion version) + { + elements.Read(reader, version); + } + + public override void Write(BinaryWriter writer) + { + elements.Write(writer); + } + + public override void ReadFull(BinaryReader reader, NetVersion version) + { + elements.ReadFull(reader, version); + } + + public override void WriteFull(BinaryWriter writer) + { + elements.WriteFull(writer); + } + } +} diff --git a/Netcode/NetColor.cs b/Netcode/NetColor.cs new file mode 100644 index 0000000..9b41b49 --- /dev/null +++ b/Netcode/NetColor.cs @@ -0,0 +1,103 @@ +using Microsoft.Xna.Framework; +using System.IO; + +namespace Netcode +{ + public sealed class NetColor : NetField + { + public byte R + { + get + { + return base.Value.R; + } + set + { + base.Value = new Color(value, G, B, A); + } + } + + public byte G + { + get + { + return base.Value.G; + } + set + { + base.Value = new Color(R, value, B, A); + } + } + + public byte B + { + get + { + return base.Value.B; + } + set + { + base.Value = new Color(R, G, value, A); + } + } + + public byte A + { + get + { + return base.Value.A; + } + set + { + base.Value = new Color(R, G, B, value); + } + } + + public NetColor() + { + } + + public NetColor(Color value) + : base(value) + { + } + + public override void Set(Color newValue) + { + if (canShortcutSet()) + { + value = newValue; + } + else if (newValue != value) + { + cleanSet(newValue); + MarkDirty(); + } + } + + public new bool Equals(NetColor other) + { + return value == other.value; + } + + public bool Equals(Color other) + { + return value == other; + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + Color newValue = default(Color); + newValue.PackedValue = reader.ReadUInt32(); + if (version.IsPriorityOver(ChangeVersion)) + { + setInterpolationTarget(newValue); + } + } + + protected override void WriteDelta(BinaryWriter writer) + { + writer.Write(value.PackedValue); + } + } +} diff --git a/Netcode/NetDictionary.cs b/Netcode/NetDictionary.cs new file mode 100644 index 0000000..eff3445 --- /dev/null +++ b/Netcode/NetDictionary.cs @@ -0,0 +1,957 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Netcode +{ + public abstract class NetDictionary : AbstractNetSerializable, IEquatable, IEnumerable, IEnumerable where TField : class, INetObject, new()where TSerialDict : IDictionary, new()where TSelf : NetDictionary + { + private class IncomingChange + { + public uint Tick; + + public bool Removal; + + public TKey Key; + + public TField Field; + + public NetVersion Reassigned; + + public IncomingChange(uint tick, bool removal, TKey key, TField field, NetVersion reassigned) + { + Tick = tick; + Removal = removal; + Key = key; + Field = field; + Reassigned = reassigned; + } + } + + private class OutgoingChange + { + public bool Removal; + + public TKey Key; + + public TField Field; + + public NetVersion Reassigned; + + public OutgoingChange(bool removal, TKey key, TField field, NetVersion reassigned) + { + Removal = removal; + Key = key; + Field = field; + Reassigned = reassigned; + } + } + + public delegate void ContentsChangeEvent(TKey key, TValue value); + + public delegate void ConflictResolveEvent(TKey key, TField rejected, TField accepted); + + public delegate void ContentsUpdateEvent(TKey key, TValue old_target_value, TValue new_target_value); + + private delegate void ReadFunc(BinaryReader reader, NetVersion version); + + private delegate void WriteFunc(BinaryWriter writer, T value); + + public struct PairsCollection : IEnumerable>, IEnumerable + { + public struct Enumerator : IEnumerator>, IDisposable, IEnumerator + { + private readonly NetDictionary _net; + + private Dictionary.Enumerator _enumerator; + + private KeyValuePair _current; + + private bool _done; + + public KeyValuePair Current => _current; + + object IEnumerator.Current + { + get + { + if (_done) + { + throw new InvalidOperationException(); + } + return _current; + } + } + + public Enumerator(NetDictionary net) + { + _net = net; + _enumerator = _net.dict.GetEnumerator(); + _current = default(KeyValuePair); + _done = false; + } + + public bool MoveNext() + { + if (_enumerator.MoveNext()) + { + KeyValuePair pair = _enumerator.Current; + _current = new KeyValuePair(pair.Key, _net.getFieldValue(pair.Value)); + return true; + } + _done = true; + _current = default(KeyValuePair); + return false; + } + + public void Dispose() + { + } + + void IEnumerator.Reset() + { + _enumerator = _net.dict.GetEnumerator(); + _current = default(KeyValuePair); + _done = false; + } + } + + private NetDictionary _net; + + public PairsCollection(NetDictionary net) + { + _net = net; + } + + public int Count() + { + return _net.dict.Count; + } + + public KeyValuePair ElementAt(int index) + { + int count = 0; + using (Enumerator enumerator = GetEnumerator()) + { + while (enumerator.MoveNext()) + { + KeyValuePair pair = enumerator.Current; + if (count == index) + { + return pair; + } + count++; + } + } + throw new ArgumentOutOfRangeException(); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(_net); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return new Enumerator(_net); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(_net); + } + } + + public struct ValuesCollection : IEnumerable, IEnumerable + { + public struct Enumerator : IEnumerator, IDisposable, IEnumerator + { + private readonly NetDictionary _net; + + private Dictionary.Enumerator _enumerator; + + private TValue _current; + + private bool _done; + + public TValue Current => _current; + + object IEnumerator.Current + { + get + { + if (_done) + { + throw new InvalidOperationException(); + } + return _current; + } + } + + public Enumerator(NetDictionary net) + { + _net = net; + _enumerator = _net.dict.GetEnumerator(); + _current = default(TValue); + _done = false; + } + + public bool MoveNext() + { + if (_enumerator.MoveNext()) + { + KeyValuePair pair = _enumerator.Current; + _current = _net.getFieldValue(pair.Value); + return true; + } + _done = true; + _current = default(TValue); + return false; + } + + public void Dispose() + { + } + + void IEnumerator.Reset() + { + _enumerator = _net.dict.GetEnumerator(); + _current = default(TValue); + _done = false; + } + } + + private NetDictionary _net; + + public ValuesCollection(NetDictionary net) + { + _net = net; + } + + public Enumerator GetEnumerator() + { + return new Enumerator(_net); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(_net); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(_net); + } + } + + public struct KeysCollection : IEnumerable, IEnumerable + { + public struct Enumerator : IEnumerator, IDisposable, IEnumerator + { + private readonly Dictionary _dict; + + private Dictionary.Enumerator _enumerator; + + private TKey _current; + + private bool _done; + + public TKey Current => _current; + + object IEnumerator.Current + { + get + { + if (_done) + { + throw new InvalidOperationException(); + } + return _current; + } + } + + public Enumerator(Dictionary dict) + { + _dict = dict; + _enumerator = _dict.GetEnumerator(); + _current = default(TKey); + _done = false; + } + + public bool MoveNext() + { + if (_enumerator.MoveNext()) + { + _current = _enumerator.Current.Key; + return true; + } + _done = true; + _current = default(TKey); + return false; + } + + public void Dispose() + { + } + + void IEnumerator.Reset() + { + _enumerator = _dict.GetEnumerator(); + _current = default(TKey); + _done = false; + } + } + + private Dictionary _dict; + + public KeysCollection(Dictionary dict) + { + _dict = dict; + } + + public bool Any() + { + return _dict.Count > 0; + } + + public TKey First() + { + using (Dictionary.Enumerator enumerator = _dict.GetEnumerator()) + { + if (enumerator.MoveNext()) + { + return enumerator.Current.Key; + } + } + return default(TKey); + } + + public bool Contains(TKey key) + { + return _dict.ContainsKey(key); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(_dict); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(_dict); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(_dict); + } + } + + public bool InterpolationWait = true; + + private Dictionary dict = new Dictionary(); + + private Dictionary dictReassigns = new Dictionary(); + + private List outgoingChanges = new List(); + + private List incomingChanges = new List(); + + public bool IsReadOnly => false; + + public TValue this[TKey key] + { + get + { + return getFieldValue(dict[key]); + } + set + { + if (!dict.ContainsKey(key)) + { + dict[key] = new TField(); + dictReassigns[key] = GetLocalVersion(); + setFieldValue(dict[key], key, value); + added(key, dict[key], dictReassigns[key]); + } + else + { + setFieldValue(dict[key], key, value); + addedEvent(key, dict[key]); + } + } + } + + public KeysCollection Keys => new KeysCollection(dict); + + public ValuesCollection Values => new ValuesCollection(this); + + public PairsCollection Pairs => new PairsCollection(this); + + public Dictionary FieldDict => dict; + + public event ContentsChangeEvent OnValueAdded; + + public event ContentsChangeEvent OnValueRemoved; + + public event ContentsUpdateEvent OnValueTargetUpdated; + + public event ConflictResolveEvent OnConflictResolve; + + public NetDictionary() + { + } + + public NetDictionary(IEnumerable> dict) + : this() + { + CopyFrom(dict); + } + + protected override bool tickImpl() + { + List triggeredChanges = null; + foreach (IncomingChange ch2 in incomingChanges) + { + if (base.Root != null && GetLocalTick() < ch2.Tick) + { + break; + } + if (triggeredChanges == null) + { + triggeredChanges = new List(); + } + triggeredChanges.Add(ch2); + } + if (triggeredChanges != null && triggeredChanges.Count > 0) + { + foreach (IncomingChange c in triggeredChanges) + { + incomingChanges.Remove(c); + } + foreach (IncomingChange ch in triggeredChanges) + { + if (ch.Removal) + { + performIncomingRemove(ch); + } + else + { + performIncomingAdd(ch); + } + } + } + return incomingChanges.Count > 0; + } + + protected abstract void setFieldValue(TField field, TKey key, TValue value); + + protected abstract TValue getFieldValue(TField field); + + protected abstract TValue getFieldTargetValue(TField field); + + protected TField createField(TKey key, TValue value) + { + TField field = new TField(); + setFieldValue(field, key, value); + return field; + } + + public void CopyFrom(IEnumerable> dict) + { + foreach (KeyValuePair pair in dict) + { + this[pair.Key] = pair.Value; + } + } + + public void Set(IEnumerable> dict) + { + Clear(); + CopyFrom(dict); + } + + public void MoveFrom(TSelf dict) + { + List> pairs = new List>(dict.Pairs); + dict.Clear(); + Set(pairs); + } + + public void SetEqualityComparer(IEqualityComparer comparer) + { + dict = new Dictionary(dict, comparer); + dictReassigns = new Dictionary(dictReassigns, comparer); + } + + private void setFieldParent(TField arg) + { + if (base.Parent != null) + { + arg.NetFields.Parent = this; + } + } + + private void added(TKey key, TField field, NetVersion reassign) + { + outgoingChanges.Add(new OutgoingChange(removal: false, key, field, reassign)); + setFieldParent(field); + MarkDirty(); + addedEvent(key, field); + foreach (IncomingChange change2 in incomingChanges) + { + if (!change2.Removal && object.Equals(change2.Key, key)) + { + clearFieldParent(change2.Field); + if (this.OnConflictResolve != null) + { + this.OnConflictResolve(key, change2.Field, field); + } + } + } + incomingChanges.RemoveAll((IncomingChange change) => object.Equals(key, change.Key)); + } + + private void addedEvent(TKey key, TField field) + { + if (this.OnValueAdded != null) + { + this.OnValueAdded(key, getFieldValue(field)); + } + } + + private void updatedEvent(TKey key, TValue old_target_value, TValue new_target_value) + { + if (this.OnValueTargetUpdated != null) + { + this.OnValueTargetUpdated(key, old_target_value, new_target_value); + } + } + + private void clearFieldParent(TField arg) + { + if (arg.NetFields.Parent == this) + { + arg.NetFields.Parent = null; + } + } + + private void removed(TKey key, TField field, NetVersion reassign) + { + outgoingChanges.Add(new OutgoingChange(removal: true, key, field, reassign)); + clearFieldParent(field); + MarkDirty(); + removedEvent(key, field); + } + + private void removedEvent(TKey key, TField field) + { + if (this.OnValueRemoved != null) + { + this.OnValueRemoved(key, getFieldValue(field)); + } + } + + public void Add(TKey key, TValue value) + { + TField field = createField(key, value); + Add(key, field); + } + + public void Add(TKey key, TField field) + { + dict.Add(key, field); + dictReassigns.Add(key, GetLocalVersion()); + added(key, field, dictReassigns[key]); + } + + public void Clear() + { + KeysCollection keys = Keys; + while (keys.Any()) + { + Remove(keys.First()); + } + outgoingChanges.RemoveAll((OutgoingChange ch) => !ch.Removal); + } + + public bool ContainsKey(TKey key) + { + return dict.ContainsKey(key); + } + + public int Count() + { + return dict.Count; + } + + public bool Remove(TKey key) + { + if (dict.ContainsKey(key)) + { + TField field = dict[key]; + NetVersion reassign = dictReassigns[key]; + dict.Remove(key); + dictReassigns.Remove(key); + removed(key, field, reassign); + return true; + } + return false; + } + + public void Filter(Func, bool> f) + { + foreach (KeyValuePair pair in Pairs.ToList()) + { + if (!f(pair)) + { + Remove(pair.Key); + } + } + } + + public bool TryGetValue(TKey key, out TValue value) + { + if (dict.TryGetValue(key, out TField field)) + { + value = getFieldValue(field); + return true; + } + value = default(TValue); + return false; + } + + public bool Equals(TSelf other) + { + return object.Equals(dict, other.dict); + } + + protected override void CleanImpl() + { + base.CleanImpl(); + outgoingChanges.Clear(); + } + + protected abstract TKey ReadKey(BinaryReader reader); + + protected abstract void WriteKey(BinaryWriter writer, TKey key); + + private void readMultiple(ReadFunc readFunc, BinaryReader reader, NetVersion version) + { + uint count = reader.Read7BitEncoded(); + for (uint i = 0u; i < count; i++) + { + readFunc(reader, version); + } + } + + private void writeMultiple(WriteFunc writeFunc, BinaryWriter writer, IEnumerable values) + { + writer.Write7BitEncoded((uint)values.Count()); + foreach (T value in values) + { + writeFunc(writer, value); + } + } + + protected virtual TField ReadFieldFull(BinaryReader reader, NetVersion version) + { + TField val = new TField(); + val.NetFields.ReadFull(reader, version); + return val; + } + + protected virtual void WriteFieldFull(BinaryWriter writer, TField field) + { + field.NetFields.WriteFull(writer); + } + + private void readAddition(BinaryReader reader, NetVersion version) + { + TKey key = ReadKey(reader); + NetVersion reassign = default(NetVersion); + reassign.Read(reader); + TField field = ReadFieldFull(reader, version); + setFieldParent(field); + queueIncomingChange(removal: false, key, field, reassign); + } + + protected virtual bool resolveConflict(TKey key, TField currentField, NetVersion currentReassign, TField incomingField, NetVersion incomingReassign) + { + if (incomingReassign.IsPriorityOver(currentReassign)) + { + clearFieldParent(currentField); + if (this.OnConflictResolve != null) + { + this.OnConflictResolve(key, currentField, incomingField); + } + return true; + } + clearFieldParent(incomingField); + if (this.OnConflictResolve != null) + { + this.OnConflictResolve(key, incomingField, currentField); + } + return false; + } + + private KeyValuePair? findConflict(TKey key) + { + foreach (IncomingChange change in incomingChanges.AsEnumerable().Reverse()) + { + if (object.Equals(change.Key, key)) + { + if (change.Removal) + { + return null; + } + return new KeyValuePair(change.Reassigned, change.Field); + } + } + if (dict.ContainsKey(key)) + { + return new KeyValuePair(dictReassigns[key], dict[key]); + } + return null; + } + + private void queueIncomingChange(bool removal, TKey key, TField field, NetVersion fieldReassign) + { + if (!removal) + { + KeyValuePair? conflict = findConflict(key); + if (conflict.HasValue && !resolveConflict(key, conflict.Value.Value, conflict.Value.Key, field, fieldReassign)) + { + return; + } + } + uint timestamp = (uint)((int)GetLocalTick() + ((InterpolationWait && base.Root != null) ? base.Root.Clock.InterpolationTicks : 0)); + incomingChanges.Add(new IncomingChange(timestamp, removal, key, field, fieldReassign)); + base.NeedsTick = true; + } + + private void performIncomingAdd(IncomingChange add) + { + dict[add.Key] = add.Field; + dictReassigns[add.Key] = add.Reassigned; + addedEvent(add.Key, add.Field); + } + + private void readRemoval(BinaryReader reader, NetVersion version) + { + TKey key = ReadKey(reader); + NetVersion reassign = default(NetVersion); + reassign.Read(reader); + queueIncomingChange(removal: true, key, null, reassign); + } + + private void readDictChange(BinaryReader reader, NetVersion version) + { + if (reader.ReadByte() != 0) + { + readRemoval(reader, version); + } + else + { + readAddition(reader, version); + } + } + + private void performIncomingRemove(IncomingChange remove) + { + if (dict.ContainsKey(remove.Key)) + { + TField field = dict[remove.Key]; + clearFieldParent(field); + dict.Remove(remove.Key); + dictReassigns.Remove(remove.Key); + removedEvent(remove.Key, field); + } + } + + private void readUpdate(BinaryReader reader, NetVersion version) + { + TKey key = ReadKey(reader); + NetVersion reassign = default(NetVersion); + reassign.Read(reader); + reader.ReadSkippable(delegate + { + int num = incomingChanges.FindLastIndex((IncomingChange ch) => !ch.Removal && object.Equals(ch.Key, key) && reassign.Equals(ch.Reassigned)); + if (num != -1) + { + TField field = incomingChanges[num].Field; + if (this.OnValueTargetUpdated != null) + { + TValue fieldTargetValue = getFieldTargetValue(field); + field.NetFields.Read(reader, version); + updatedEvent(key, fieldTargetValue, getFieldTargetValue(field)); + } + else + { + field.NetFields.Read(reader, version); + } + } + else if (dict.ContainsKey(key) && dictReassigns[key].Equals(reassign)) + { + TField val = dict[key]; + if (this.OnValueTargetUpdated != null) + { + TValue fieldTargetValue2 = getFieldTargetValue(val); + val.NetFields.Read(reader, version); + updatedEvent(key, fieldTargetValue2, getFieldTargetValue(val)); + } + else + { + val.NetFields.Read(reader, version); + } + } + }); + } + + public override void Read(BinaryReader reader, NetVersion version) + { + readMultiple(readDictChange, reader, version); + readMultiple(readUpdate, reader, version); + } + + public override void ReadFull(BinaryReader reader, NetVersion version) + { + dict.Clear(); + dictReassigns.Clear(); + outgoingChanges.Clear(); + incomingChanges.Clear(); + int count = reader.ReadInt32(); + for (int i = 0; i < count; i++) + { + TKey key = ReadKey(reader); + NetVersion reassign = default(NetVersion); + reassign.Read(reader); + TField field = ReadFieldFull(reader, version); + dict.Add(key, field); + dictReassigns.Add(key, reassign); + setFieldParent(field); + addedEvent(key, field); + } + } + + private void writeAddition(BinaryWriter writer, OutgoingChange update) + { + WriteKey(writer, update.Key); + update.Reassigned.Write(writer); + WriteFieldFull(writer, update.Field); + } + + private void writeRemoval(BinaryWriter writer, OutgoingChange update) + { + WriteKey(writer, update.Key); + update.Reassigned.Write(writer); + } + + private void writeDictChange(BinaryWriter writer, OutgoingChange ch) + { + if (ch.Removal) + { + writer.Write((byte)1); + writeRemoval(writer, ch); + } + else + { + writer.Write((byte)0); + writeAddition(writer, ch); + } + } + + private void writeUpdate(BinaryWriter writer, OutgoingChange update) + { + WriteKey(writer, update.Key); + update.Reassigned.Write(writer); + writer.WriteSkippable(delegate + { + update.Field.NetFields.Write(writer); + }); + } + + private IEnumerable updates() + { + foreach (KeyValuePair pair in dict) + { + if (pair.Value.NetFields.Dirty) + { + yield return new OutgoingChange(removal: false, pair.Key, pair.Value, dictReassigns[pair.Key]); + } + } + foreach (OutgoingChange removal in outgoingChanges.Where((OutgoingChange ch) => ch.Removal)) + { + if (removal.Field.NetFields.Dirty) + { + yield return removal; + } + } + } + + public override void Write(BinaryWriter writer) + { + writeMultiple(writeDictChange, writer, outgoingChanges); + writeMultiple(writeUpdate, writer, updates()); + } + + public override void WriteFull(BinaryWriter writer) + { + writer.Write(Count()); + foreach (TKey key in dict.Keys) + { + WriteKey(writer, key); + dictReassigns[key].Write(writer); + WriteFieldFull(writer, dict[key]); + } + } + + public IEnumerator GetEnumerator() + { + TSerialDict serial = new TSerialDict(); + foreach (KeyValuePair kvp in dict) + { + serial.Add(kvp.Key, getFieldValue(kvp.Value)); + } + return new List + { + serial + }.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + protected override void ForEachChild(Action childAction) + { + foreach (IncomingChange ch in incomingChanges) + { + if (ch.Field != null) + { + childAction(ch.Field.NetFields); + } + } + foreach (TField field in dict.Values) + { + childAction(field.NetFields); + } + } + + public void Add(TSerialDict dict) + { + Set(dict); + } + + protected override void ValidateChildren() + { + if ((base.Parent != null || base.Root == this) && !base.NeedsTick) + { + ForEachChild(ValidateChild); + } + } + } +} diff --git a/Netcode/NetDouble.cs b/Netcode/NetDouble.cs new file mode 100644 index 0000000..0af5148 --- /dev/null +++ b/Netcode/NetDouble.cs @@ -0,0 +1,48 @@ +using System.IO; + +namespace Netcode +{ + public sealed class NetDouble : NetField + { + public NetDouble() + { + } + + public NetDouble(double value) + : base(value) + { + } + + public override void Set(double newValue) + { + if (canShortcutSet()) + { + value = newValue; + } + else if (newValue != value) + { + cleanSet(newValue); + MarkDirty(); + } + } + + protected override double interpolate(double startValue, double endValue, float factor) + { + return startValue + (endValue - startValue) * (double)factor; + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + double newValue = reader.ReadDouble(); + if (version.IsPriorityOver(ChangeVersion)) + { + setInterpolationTarget(newValue); + } + } + + protected override void WriteDelta(BinaryWriter writer) + { + writer.Write(value); + } + } +} diff --git a/Netcode/NetEnum.cs b/Netcode/NetEnum.cs new file mode 100644 index 0000000..03cb594 --- /dev/null +++ b/Netcode/NetEnum.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Netcode +{ + public class NetEnum : NetFieldBase>, IEnumerable, IEnumerable where T : struct, IConvertible + { + private bool xmlInitialized; + + public NetEnum() + { + } + + public NetEnum(T value) + : base(value) + { + } + + public override void Set(T newValue) + { + if (!EqualityComparer.Default.Equals(newValue, value)) + { + cleanSet(newValue); + MarkDirty(); + } + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + T newValue = (T)Enum.ToObject(typeof(T), reader.ReadInt16()); + if (version.IsPriorityOver(ChangeVersion)) + { + setInterpolationTarget(newValue); + } + } + + protected override void WriteDelta(BinaryWriter writer) + { + writer.Write(Convert.ToInt16(value)); + } + + public static implicit operator int(NetEnum netField) + { + return Convert.ToInt32(netField.Get()); + } + + public static implicit operator short(NetEnum netField) + { + return Convert.ToInt16(netField.Get()); + } + + public IEnumerator GetEnumerator() + { + return Enumerable.Repeat(Convert.ToString(Get()), 1).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(string value) + { + if (xmlInitialized || base.Parent != null) + { + throw new InvalidOperationException(GetType().Name + " already has value " + ToString()); + } + cleanSet((T)Enum.Parse(typeof(T), value)); + xmlInitialized = true; + } + } +} diff --git a/Netcode/NetEvent0.cs b/Netcode/NetEvent0.cs new file mode 100644 index 0000000..4043e58 --- /dev/null +++ b/Netcode/NetEvent0.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; + +namespace Netcode +{ + public class NetEvent0 : AbstractNetSerializable + { + public delegate void Event(); + + public readonly NetInt Counter = new NetInt(); + + private int currentCount; + + public event Event onEvent; + + public NetEvent0(bool interpolate = false) + { + Counter.InterpolationEnabled = interpolate; + } + + public void Fire() + { + int num = ++Counter.Value; + Poll(); + } + + public void Poll() + { + if (Counter.Value != currentCount) + { + currentCount = Counter.Value; + if (this.onEvent != null) + { + this.onEvent(); + } + } + } + + public void Clear() + { + Counter.Set(0); + currentCount = 0; + } + + public override void Read(BinaryReader reader, NetVersion version) + { + Counter.Read(reader, version); + } + + public override void ReadFull(BinaryReader reader, NetVersion version) + { + Counter.ReadFull(reader, version); + currentCount = Counter.Value; + } + + public override void Write(BinaryWriter writer) + { + Counter.Write(writer); + } + + public override void WriteFull(BinaryWriter writer) + { + Counter.WriteFull(writer); + } + + protected override void ForEachChild(Action childAction) + { + childAction(Counter); + } + } +} diff --git a/Netcode/NetEvent1.cs b/Netcode/NetEvent1.cs new file mode 100644 index 0000000..d9d66c6 --- /dev/null +++ b/Netcode/NetEvent1.cs @@ -0,0 +1,19 @@ +using System.IO; + +namespace Netcode +{ + public class NetEvent1 : AbstractNetEvent1 where T : NetEventArg, new() + { + protected override T readEventArg(BinaryReader reader, NetVersion version) + { + T arg = new T(); + arg.Read(reader); + return arg; + } + + protected override void writeEventArg(BinaryWriter writer, T eventArg) + { + eventArg.Write(writer); + } + } +} diff --git a/Netcode/NetEvent1Field.cs b/Netcode/NetEvent1Field.cs new file mode 100644 index 0000000..dd91fa5 --- /dev/null +++ b/Netcode/NetEvent1Field.cs @@ -0,0 +1,21 @@ +using System.IO; + +namespace Netcode +{ + public class NetEvent1Field : AbstractNetEvent1 where TField : NetField, new() + { + protected override T readEventArg(BinaryReader reader, NetVersion version) + { + TField val = new TField(); + val.ReadFull(reader, version); + return val.Value; + } + + protected override void writeEventArg(BinaryWriter writer, T eventArg) + { + TField val = new TField(); + val.Value = eventArg; + val.WriteFull(writer); + } + } +} diff --git a/Netcode/NetEventArg.cs b/Netcode/NetEventArg.cs new file mode 100644 index 0000000..816f91f --- /dev/null +++ b/Netcode/NetEventArg.cs @@ -0,0 +1,11 @@ +using System.IO; + +namespace Netcode +{ + public interface NetEventArg + { + void Read(BinaryReader reader); + + void Write(BinaryWriter writer); + } +} diff --git a/Netcode/NetEventBinary.cs b/Netcode/NetEventBinary.cs new file mode 100644 index 0000000..00d64a9 --- /dev/null +++ b/Netcode/NetEventBinary.cs @@ -0,0 +1,52 @@ +using System; +using System.IO; + +namespace Netcode +{ + public class NetEventBinary : AbstractNetEvent1 + { + public delegate void ArgWriter(BinaryWriter writer); + + public void Fire(ArgWriter argWriter) + { + byte[] bytes; + using (MemoryStream ms = new MemoryStream()) + { + using (BinaryWriter writer = new BinaryWriter(ms)) + { + argWriter(writer); + ms.Position = 0L; + bytes = new byte[ms.Length]; + ms.Read(bytes, 0, (int)ms.Length); + } + } + Fire(bytes); + } + + public void AddReaderHandler(Action handler) + { + base.onEvent += delegate(byte[] bytes) + { + using (MemoryStream input = new MemoryStream(bytes)) + { + using (BinaryReader obj = new BinaryReader(input)) + { + handler(obj); + } + } + }; + } + + protected override byte[] readEventArg(BinaryReader reader, NetVersion version) + { + int count = reader.ReadInt32(); + return reader.ReadBytes(count); + } + + protected override void writeEventArg(BinaryWriter writer, byte[] arg) + { + writer.Write(arg.Length); + writer.Write(arg); + } + } +} diff --git a/Netcode/NetExtendableRef.cs b/Netcode/NetExtendableRef.cs new file mode 100644 index 0000000..830e19b --- /dev/null +++ b/Netcode/NetExtendableRef.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; + +namespace Netcode +{ + public class NetExtendableRef : NetRefBase where T : class, INetObject where TSelf : NetExtendableRef + { + public NetExtendableRef() + { + base.notifyOnTargetValueChange = true; + } + + public NetExtendableRef(T value) + : this() + { + cleanSet(value); + } + + protected override void ForEachChild(Action childAction) + { + if (targetValue != null) + { + childAction(targetValue.NetFields); + } + } + + protected override void ReadValueFull(T value, BinaryReader reader, NetVersion version) + { + value.NetFields.ReadFull(reader, version); + } + + protected override void ReadValueDelta(BinaryReader reader, NetVersion version) + { + targetValue.NetFields.Read(reader, version); + } + + private void clearValueParent(T targetValue) + { + if (targetValue.NetFields.Parent == this) + { + targetValue.NetFields.Parent = null; + } + } + + private void setValueParent(T targetValue) + { + if (base.Parent != null || base.Root == this) + { + targetValue.NetFields.Parent = this; + } + targetValue.NetFields.MarkClean(); + } + + protected override void targetValueChanged(T oldValue, T newValue) + { + base.targetValueChanged(oldValue, newValue); + if (oldValue != null) + { + clearValueParent(oldValue); + } + if (newValue != null) + { + setValueParent(newValue); + } + } + + protected override void WriteValueFull(BinaryWriter writer) + { + targetValue.NetFields.WriteFull(writer); + } + + protected override void WriteValueDelta(BinaryWriter writer) + { + targetValue.NetFields.Write(writer); + } + } +} diff --git a/Netcode/NetField.cs b/Netcode/NetField.cs new file mode 100644 index 0000000..a77a4b7 --- /dev/null +++ b/Netcode/NetField.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Netcode +{ + public abstract class NetField : NetFieldBase, IEnumerable, IEnumerable where TSelf : NetField + { + private bool xmlInitialized; + + public NetField() + { + } + + public NetField(T value) + : base(value) + { + } + + public IEnumerator GetEnumerator() + { + return Enumerable.Repeat(Get(), 1).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(T value) + { + if (xmlInitialized || base.Parent != null) + { + throw new InvalidOperationException(GetType().Name + " already has value " + ToString()); + } + cleanSet(value); + xmlInitialized = true; + } + } +} diff --git a/Netcode/NetFieldBase.cs b/Netcode/NetFieldBase.cs new file mode 100644 index 0000000..a2c926c --- /dev/null +++ b/Netcode/NetFieldBase.cs @@ -0,0 +1,366 @@ +using System; +using System.IO; + +namespace Netcode +{ + public abstract class NetFieldBase : AbstractNetSerializable, IEquatable, InterpolationCancellable where TSelf : NetFieldBase + { + public delegate void FieldChange(TSelf field, T oldValue, T newValue); + + [Flags] + protected enum NetFieldBaseBool : byte + { + None = 0x0, + InterpolationEnabled = 0x1, + ExtrapolationEnabled = 0x2, + InterpolationWait = 0x4, + notifyOnTargetValueChange = 0x8 + } + + protected NetFieldBaseBool _bools; + + protected uint interpolationStartTick; + + protected T value; + + protected T previousValue; + + protected T targetValue; + + public bool InterpolationEnabled + { + get + { + return (_bools & NetFieldBaseBool.InterpolationEnabled) != 0; + } + set + { + if (value) + { + _bools |= NetFieldBaseBool.InterpolationEnabled; + } + else + { + _bools &= ~NetFieldBaseBool.InterpolationEnabled; + } + } + } + + public bool ExtrapolationEnabled + { + get + { + return (_bools & NetFieldBaseBool.ExtrapolationEnabled) != 0; + } + set + { + if (value) + { + _bools |= NetFieldBaseBool.ExtrapolationEnabled; + } + else + { + _bools &= ~NetFieldBaseBool.ExtrapolationEnabled; + } + } + } + + public bool InterpolationWait + { + get + { + return (_bools & NetFieldBaseBool.InterpolationWait) != 0; + } + set + { + if (value) + { + _bools |= NetFieldBaseBool.InterpolationWait; + } + else + { + _bools &= ~NetFieldBaseBool.InterpolationWait; + } + } + } + + protected bool notifyOnTargetValueChange + { + get + { + return (_bools & NetFieldBaseBool.notifyOnTargetValueChange) != 0; + } + set + { + if (value) + { + _bools |= NetFieldBaseBool.notifyOnTargetValueChange; + } + else + { + _bools &= ~NetFieldBaseBool.notifyOnTargetValueChange; + } + } + } + + public T TargetValue => targetValue; + + public T Value + { + get + { + return value; + } + set + { + Set(value); + } + } + + public event FieldChange fieldChangeEvent; + + public event FieldChange fieldChangeVisibleEvent; + + public NetFieldBase() + { + InterpolationWait = true; + value = default(T); + previousValue = default(T); + targetValue = default(T); + } + + public NetFieldBase(T value) + : this() + { + cleanSet(value); + } + + public TSelf Interpolated(bool interpolate, bool wait) + { + InterpolationEnabled = interpolate; + InterpolationWait = wait; + return (TSelf)this; + } + + protected virtual int InterpolationTicks() + { + if (base.Root == null) + { + return 0; + } + return base.Root.Clock.InterpolationTicks; + } + + protected float InterpolationFactor() + { + return (float)(double)(base.Root.Clock.GetLocalTick() - interpolationStartTick) / (float)InterpolationTicks(); + } + + public bool IsInterpolating() + { + if (InterpolationEnabled) + { + return base.NeedsTick; + } + return false; + } + + public bool IsChanging() + { + return base.NeedsTick; + } + + protected override bool tickImpl() + { + if (base.Root != null && InterpolationTicks() > 0) + { + float factor = InterpolationFactor(); + bool shouldExtrapolate = ExtrapolationEnabled && ChangeVersion[0] == base.Root.Clock.netVersion[0]; + if ((factor < 1f && InterpolationEnabled) || (shouldExtrapolate && factor < 3f)) + { + value = interpolate(previousValue, targetValue, factor); + return true; + } + if (factor < 1f && InterpolationWait) + { + value = previousValue; + return true; + } + } + T oldValue = previousValue; + CancelInterpolation(); + if (this.fieldChangeVisibleEvent != null) + { + this.fieldChangeVisibleEvent((TSelf)this, oldValue, value); + } + return false; + } + + public void CancelInterpolation() + { + if (base.NeedsTick) + { + value = targetValue; + previousValue = default(T); + base.NeedsTick = false; + } + } + + public T Get() + { + return value; + } + + protected virtual T interpolate(T startValue, T endValue, float factor) + { + return startValue; + } + + public abstract void Set(T newValue); + + protected bool canShortcutSet() + { + if (Dirty && this.fieldChangeEvent == null) + { + return this.fieldChangeVisibleEvent == null; + } + return false; + } + + protected virtual void targetValueChanged(T oldValue, T newValue) + { + } + + protected void cleanSet(T newValue) + { + T oldValue = value; + T oldTargetValue = targetValue; + targetValue = newValue; + value = newValue; + previousValue = default(T); + base.NeedsTick = false; + if (notifyOnTargetValueChange) + { + targetValueChanged(oldTargetValue, newValue); + } + if (this.fieldChangeEvent != null) + { + this.fieldChangeEvent((TSelf)this, oldValue, newValue); + } + if (this.fieldChangeVisibleEvent != null) + { + this.fieldChangeVisibleEvent((TSelf)this, oldValue, newValue); + } + } + + protected virtual bool setUpInterpolation(T oldValue, T newValue) + { + return true; + } + + protected void setInterpolationTarget(T newValue) + { + T oldValue = value; + if (!InterpolationWait || base.Root == null || !setUpInterpolation(oldValue, newValue)) + { + cleanSet(newValue); + return; + } + T oldTargetValue = targetValue; + previousValue = oldValue; + base.NeedsTick = true; + targetValue = newValue; + interpolationStartTick = base.Root.Clock.GetLocalTick(); + if (notifyOnTargetValueChange) + { + targetValueChanged(oldTargetValue, newValue); + } + if (this.fieldChangeEvent != null) + { + this.fieldChangeEvent((TSelf)this, oldValue, newValue); + } + } + + protected abstract void ReadDelta(BinaryReader reader, NetVersion version); + + protected abstract void WriteDelta(BinaryWriter writer); + + public override void ReadFull(BinaryReader reader, NetVersion version) + { + ReadDelta(reader, version); + CancelInterpolation(); + ChangeVersion.Merge(version); + } + + public override void WriteFull(BinaryWriter writer) + { + WriteDelta(writer); + } + + public override void Read(BinaryReader reader, NetVersion version) + { + ReadDelta(reader, version); + ChangeVersion.Merge(version); + } + + public override void Write(BinaryWriter writer) + { + WriteDelta(writer); + } + + public static implicit operator T(NetFieldBase netField) + { + if (netField == null) + { + return default(T); + } + return netField.value; + } + + public override string ToString() + { + if (value != null) + { + return value.ToString(); + } + return "null"; + } + + public override bool Equals(object obj) + { + if (!(obj is TSelf) || !Equals(obj as TSelf)) + { + return object.Equals(Value, obj); + } + return true; + } + + public bool Equals(TSelf other) + { + return object.Equals(Value, other.Value); + } + + public static bool operator ==(NetFieldBase self, TSelf other) + { + if ((object)self != other) + { + return object.Equals(self, other); + } + return true; + } + + public static bool operator !=(NetFieldBase self, TSelf other) + { + if ((object)self != other) + { + return !object.Equals(self, other); + } + return false; + } + + public override int GetHashCode() + { + return ((value != null) ? value.GetHashCode() : 0) ^ -858436897; + } + } +} diff --git a/Netcode/NetFieldDictionary.cs b/Netcode/NetFieldDictionary.cs new file mode 100644 index 0000000..cf5937c --- /dev/null +++ b/Netcode/NetFieldDictionary.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; + +namespace Netcode +{ + public abstract class NetFieldDictionary : NetDictionary where TField : NetField, new()where TSerialDict : IDictionary, new()where TSelf : NetDictionary + { + public NetFieldDictionary() + { + } + + public NetFieldDictionary(IEnumerable> pairs) + : base(pairs) + { + } + + protected override void setFieldValue(TField field, TKey key, TValue value) + { + field.Value = value; + } + + protected override TValue getFieldValue(TField field) + { + return field.Value; + } + + protected override TValue getFieldTargetValue(TField field) + { + return field.TargetValue; + } + } +} diff --git a/Netcode/NetFields.cs b/Netcode/NetFields.cs new file mode 100644 index 0000000..7e82883 --- /dev/null +++ b/Netcode/NetFields.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; + +namespace Netcode +{ + public class NetFields : AbstractNetSerializable + { + private List fields = new List(); + + public void AddFields(params INetSerializable[] fields) + { + foreach (INetSerializable field in fields) + { + AddField(field); + } + } + + public void CancelInterpolation() + { + foreach (INetSerializable field in fields) + { + if (field is InterpolationCancellable) + { + (field as InterpolationCancellable).CancelInterpolation(); + } + } + } + + public void AddField(INetSerializable field) + { + if (field.Parent != null) + { + throw new InvalidOperationException("Attempt to add a field to more than one tree"); + } + if (base.Parent != null) + { + throw new InvalidOperationException("Cannot add new fields once this NetFields is part of a tree"); + } + fields.Add(field); + } + + public override void Read(BinaryReader reader, NetVersion version) + { + BitArray dirtyBits = reader.ReadBitArray(); + if (fields.Count != dirtyBits.Length) + { + throw new InvalidOperationException(); + } + for (int i = 0; i < fields.Count; i++) + { + if (dirtyBits[i]) + { + fields[i].Read(reader, version); + } + } + } + + public override void Write(BinaryWriter writer) + { + BitArray dirtyBits = new BitArray(fields.Count); + for (int j = 0; j < fields.Count; j++) + { + dirtyBits[j] = fields[j].Dirty; + } + writer.WriteBitArray(dirtyBits); + for (int i = 0; i < fields.Count; i++) + { + if (dirtyBits[i]) + { + INetSerializable netSerializable = fields[i]; + writer.Push(Convert.ToString(i)); + netSerializable.Write(writer); + writer.Pop(); + } + } + } + + public override void ReadFull(BinaryReader reader, NetVersion version) + { + foreach (INetSerializable field in fields) + { + field.ReadFull(reader, version); + } + } + + public override void WriteFull(BinaryWriter writer) + { + for (int i = 0; i < fields.Count; i++) + { + INetSerializable netSerializable = fields[i]; + writer.Push(Convert.ToString(i)); + netSerializable.WriteFull(writer); + writer.Pop(); + } + } + + public virtual void CopyFrom(NetFields source) + { + using (MemoryStream stream = new MemoryStream()) + { + using (BinaryWriter writer = new BinaryWriter(stream)) + { + using (BinaryReader reader = new BinaryReader(stream)) + { + source.WriteFull(writer); + stream.Seek(0L, SeekOrigin.Begin); + if (base.Root == null) + { + ReadFull(reader, new NetClock().netVersion); + } + else + { + ReadFull(reader, base.Root.Clock.netVersion); + } + MarkClean(); + } + } + } + } + + protected override void ForEachChild(Action childAction) + { + foreach (INetSerializable field in fields) + { + childAction(field); + } + } + } +} diff --git a/Netcode/NetFloat.cs b/Netcode/NetFloat.cs new file mode 100644 index 0000000..77f7306 --- /dev/null +++ b/Netcode/NetFloat.cs @@ -0,0 +1,48 @@ +using System.IO; + +namespace Netcode +{ + public class NetFloat : NetField + { + public NetFloat() + { + } + + public NetFloat(float value) + : base(value) + { + } + + public override void Set(float newValue) + { + if (canShortcutSet()) + { + value = newValue; + } + else if (newValue != value) + { + cleanSet(newValue); + MarkDirty(); + } + } + + protected override float interpolate(float startValue, float endValue, float factor) + { + return startValue + (endValue - startValue) * factor; + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + float newValue = reader.ReadSingle(); + if (version.IsPriorityOver(ChangeVersion)) + { + setInterpolationTarget(newValue); + } + } + + protected override void WriteDelta(BinaryWriter writer) + { + writer.Write(value); + } + } +} diff --git a/Netcode/NetGuid.cs b/Netcode/NetGuid.cs new file mode 100644 index 0000000..00d369f --- /dev/null +++ b/Netcode/NetGuid.cs @@ -0,0 +1,44 @@ +using System; +using System.IO; + +namespace Netcode +{ + public sealed class NetGuid : NetField + { + public NetGuid() + { + } + + public NetGuid(Guid value) + : base(value) + { + } + + public override void Set(Guid newValue) + { + if (canShortcutSet()) + { + value = newValue; + } + else if (newValue != value) + { + cleanSet(newValue); + MarkDirty(); + } + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + Guid newValue = reader.ReadGuid(); + if (version.IsPriorityOver(ChangeVersion)) + { + setInterpolationTarget(newValue); + } + } + + protected override void WriteDelta(BinaryWriter writer) + { + writer.WriteGuid(value); + } + } +} diff --git a/Netcode/NetGuidDictionary.cs b/Netcode/NetGuidDictionary.cs new file mode 100644 index 0000000..64b1916 --- /dev/null +++ b/Netcode/NetGuidDictionary.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Netcode +{ + public class NetGuidDictionary : NetFieldDictionary, NetGuidDictionary> where TField : NetField, new() + { + public NetGuidDictionary() + { + } + + public NetGuidDictionary(IEnumerable> pairs) + : base(pairs) + { + } + + protected override Guid ReadKey(BinaryReader reader) + { + return reader.ReadGuid(); + } + + protected override void WriteKey(BinaryWriter writer, Guid key) + { + writer.WriteGuid(key); + } + } +} diff --git a/Netcode/NetInt.cs b/Netcode/NetInt.cs new file mode 100644 index 0000000..8734f1a --- /dev/null +++ b/Netcode/NetInt.cs @@ -0,0 +1,58 @@ +using System.IO; + +namespace Netcode +{ + public sealed class NetInt : NetField + { + public NetInt() + { + } + + public NetInt(int value) + : base(value) + { + } + + public override void Set(int newValue) + { + if (canShortcutSet()) + { + value = newValue; + } + else if (newValue != value) + { + cleanSet(newValue); + MarkDirty(); + } + } + + public new bool Equals(NetInt other) + { + return value == other.value; + } + + public bool Equals(int other) + { + return value == other; + } + + protected override int interpolate(int startValue, int endValue, float factor) + { + return startValue + (int)((float)(endValue - startValue) * factor); + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + int newValue = reader.ReadInt32(); + if (version.IsPriorityOver(ChangeVersion)) + { + setInterpolationTarget(newValue); + } + } + + protected override void WriteDelta(BinaryWriter writer) + { + writer.Write(value); + } + } +} diff --git a/Netcode/NetIntDelta.cs b/Netcode/NetIntDelta.cs new file mode 100644 index 0000000..4ecb137 --- /dev/null +++ b/Netcode/NetIntDelta.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; + +namespace Netcode +{ + public sealed class NetIntDelta : NetField + { + private int networkValue; + + public int DirtyThreshold; + + public int? Minimum; + + public int? Maximum; + + public NetIntDelta() + { + Interpolated(interpolate: false, wait: false); + } + + public NetIntDelta(int value) + : base(value) + { + Interpolated(interpolate: false, wait: false); + } + + private int fixRange(int value) + { + if (Minimum.HasValue) + { + value = Math.Max(Minimum.Value, value); + } + if (Maximum.HasValue) + { + value = Math.Min(Maximum.Value, value); + } + return value; + } + + public override void Set(int newValue) + { + newValue = fixRange(newValue); + if (newValue != value) + { + cleanSet(newValue); + if (Math.Abs(newValue - networkValue) > DirtyThreshold) + { + MarkDirty(); + } + } + } + + protected override int interpolate(int startValue, int endValue, float factor) + { + return startValue + (int)((float)(endValue - startValue) * factor); + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + int delta = reader.ReadInt32(); + networkValue = fixRange(networkValue + delta); + setInterpolationTarget(fixRange(targetValue + delta)); + } + + protected override void WriteDelta(BinaryWriter writer) + { + writer.Write(targetValue - networkValue); + networkValue = targetValue; + } + + public override void ReadFull(BinaryReader reader, NetVersion version) + { + int fullValue = reader.ReadInt32(); + cleanSet(fullValue); + networkValue = fullValue; + ChangeVersion.Merge(version); + } + + public override void WriteFull(BinaryWriter writer) + { + writer.Write(targetValue); + networkValue = targetValue; + } + } +} diff --git a/Netcode/NetIntList.cs b/Netcode/NetIntList.cs new file mode 100644 index 0000000..ad281d8 --- /dev/null +++ b/Netcode/NetIntList.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; + +namespace Netcode +{ + public sealed class NetIntList : NetList + { + public NetIntList() + { + } + + public NetIntList(IEnumerable values) + : base(values) + { + } + + public NetIntList(int capacity) + : base(capacity) + { + } + + public override bool Contains(int item) + { + using (Enumerator enumerator = GetEnumerator()) + { + while (enumerator.MoveNext()) + { + if (enumerator.Current == item) + { + return true; + } + } + } + return false; + } + + public override int IndexOf(int item) + { + NetInt count = base.count; + for (int i = 0; i < (int)count; i++) + { + if (array.Value[i] == item) + { + return i; + } + } + return -1; + } + } +} diff --git a/Netcode/NetList.cs b/Netcode/NetList.cs new file mode 100644 index 0000000..4896759 --- /dev/null +++ b/Netcode/NetList.cs @@ -0,0 +1,432 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; + +namespace Netcode +{ + public class NetList : AbstractNetSerializable, IList, ICollection, IEnumerable, IEnumerable, IEquatable> where TField : NetField, new() + { + public delegate void ElementChangedEvent(NetList list, int index, T oldValue, T newValue); + + public delegate void ArrayReplacedEvent(NetList list, IList before, IList after); + + public struct Enumerator : IEnumerator, IDisposable, IEnumerator + { + private readonly NetList _list; + + private int _index; + + private T _current; + + private bool _done; + + public T Current => _current; + + object IEnumerator.Current + { + get + { + if (_done) + { + throw new InvalidOperationException(); + } + return _current; + } + } + + public Enumerator(NetList list) + { + _list = list; + _index = 0; + _current = default(T); + _done = false; + } + + public bool MoveNext() + { + int count = _list.count.Value; + if (_index < count) + { + _current = _list.array.Value[_index]; + _index++; + return true; + } + _done = true; + _current = default(T); + return false; + } + + public void Dispose() + { + } + + void IEnumerator.Reset() + { + _index = 0; + _current = default(T); + _done = false; + } + } + + private const int initialSize = 10; + + private const double resizeFactor = 1.5; + + protected readonly NetInt count = new NetInt(0).Interpolated(interpolate: false, wait: false); + + protected readonly NetRef> array = new NetRef>(new NetArray(10)).Interpolated(interpolate: false, wait: false); + + public T this[int index] + { + get + { + if (index >= Count || index < 0) + { + throw new ArgumentOutOfRangeException(); + } + return array.Value[index]; + } + set + { + if (index >= Count || index < 0) + { + throw new ArgumentOutOfRangeException(); + } + array.Value[index] = value; + } + } + + public int Count => count; + + public int Capacity => array.Value.Count; + + public bool IsReadOnly => false; + + public event ElementChangedEvent OnElementChanged; + + public event ArrayReplacedEvent OnArrayReplaced; + + public NetList() + { + hookArray(array.Value); + array.fieldChangeVisibleEvent += delegate(NetRef> arrayRef, NetArray oldArray, NetArray newArray) + { + if (newArray != null) + { + hookArray(newArray); + } + if (this.OnArrayReplaced != null) + { + this.OnArrayReplaced(this, oldArray, newArray); + } + }; + } + + public NetList(IEnumerable values) + : this() + { + foreach (T value in values) + { + Add(value); + } + } + + public NetList(int capacity) + : this() + { + Resize(capacity); + } + + private void hookField(int index, TField field) + { + if (!((NetFieldBase)field == (TField)null)) + { + field.fieldChangeVisibleEvent += delegate(TField f, T oldValue, T newValue) + { + if (this.OnElementChanged != null) + { + this.OnElementChanged(this, index, oldValue, newValue); + } + }; + } + } + + private void hookArray(NetArray array) + { + for (int i = 0; i < array.Count; i++) + { + hookField(i, array.Fields[i]); + } + array.OnFieldCreate += hookField; + } + + private void Resize(int capacity) + { + count.Set(Math.Min(capacity, count)); + NetArray oldArray = array.Value; + NetArray newArray = new NetArray(capacity); + array.Value = newArray; + for (int i = 0; i < capacity && i < Count; i++) + { + T tmp = oldArray[i]; + oldArray[i] = default(T); + array.Value[i] = tmp; + } + } + + private void EnsureCapacity(int neededCapacity) + { + if (neededCapacity > Capacity) + { + int newCapacity = (int)((double)Capacity * 1.5); + while (neededCapacity > newCapacity) + { + newCapacity = (int)((double)newCapacity * 1.5); + } + Resize(newCapacity); + } + } + + public void Add(T item) + { + EnsureCapacity(Count + 1); + array.Value[Count] = item; + count.Set((int)count + 1); + } + + public void Clear() + { + count.Set(0); + Resize(10); + fillNull(); + } + + private void fillNull() + { + for (int i = 0; i < Capacity; i++) + { + array.Value[i] = default(T); + } + } + + public void CopyFrom(IList list) + { + if (list != this) + { + EnsureCapacity(list.Count); + fillNull(); + _ = (int)count; + count.Set(list.Count); + for (int i = 0; i < list.Count; i++) + { + array.Value[i] = list[i]; + } + } + } + + public void Set(IList list) + { + CopyFrom(list); + } + + public void MoveFrom(NetList list) + { + List values = new List(list); + list.Clear(); + Set(values); + } + + public bool Any() + { + return count.Value > 0; + } + + public virtual bool Contains(T item) + { + using (Enumerator enumerator = GetEnumerator()) + { + while (enumerator.MoveNext()) + { + if (object.Equals(enumerator.Current, item)) + { + return true; + } + } + } + return false; + } + + public void CopyTo(T[] array, int arrayIndex) + { + if (array == null) + { + throw new ArgumentNullException(); + } + if (arrayIndex < 0) + { + throw new ArgumentOutOfRangeException(); + } + if (Count - arrayIndex > array.Length) + { + throw new ArgumentException(); + } + using (Enumerator enumerator = GetEnumerator()) + { + while (enumerator.MoveNext()) + { + T value = enumerator.Current; + array[arrayIndex++] = value; + } + } + } + + public List GetRange(int index, int count) + { + List result = new List(); + for (int i = index; i < index + count; i++) + { + result.Add(this[i]); + } + return result; + } + + public void AddRange(IEnumerable collection) + { + foreach (T value in collection) + { + Add(value); + } + } + + public void RemoveRange(int index, int count) + { + for (int i = 0; i < count; i++) + { + RemoveAt(index); + } + } + + public bool Equals(NetList other) + { + return object.Equals(array, other.array); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(this); + } + + public virtual int IndexOf(T item) + { + for (int i = 0; i < Count; i++) + { + if (object.Equals(array.Value[i], item)) + { + return i; + } + } + return -1; + } + + public void Insert(int index, T item) + { + if (index > Count || index < 0) + { + throw new ArgumentOutOfRangeException(); + } + EnsureCapacity(Count + 1); + count.Set((int)count + 1); + for (int i = Count - 1; i > index; i--) + { + T tmp = array.Value[i - 1]; + array.Value[i - 1] = default(T); + array.Value[i] = tmp; + } + array.Value[index] = item; + } + + public override void Read(BinaryReader reader, NetVersion version) + { + count.Read(reader, version); + array.Read(reader, version); + } + + public override void ReadFull(BinaryReader reader, NetVersion version) + { + count.ReadFull(reader, version); + array.ReadFull(reader, version); + } + + public bool Remove(T item) + { + int index = IndexOf(item); + if (index != -1) + { + RemoveAt(index); + return true; + } + return false; + } + + public void RemoveAt(int index) + { + if (index < 0 || index >= Count) + { + throw new ArgumentOutOfRangeException(); + } + count.Set((int)count - 1); + for (int i = index; i < Count; i++) + { + T tmp = array.Value[i + 1]; + array.Value[i + 1] = default(T); + array.Value[i] = tmp; + } + array.Value[Count] = default(T); + } + + public void Filter(Func f) + { + for (int i = Count - 1; i >= 0; i--) + { + if (!f(this[i])) + { + RemoveAt(i); + } + } + } + + public override void Write(BinaryWriter writer) + { + count.Write(writer); + array.Write(writer); + } + + public override void WriteFull(BinaryWriter writer) + { + count.WriteFull(writer); + array.WriteFull(writer); + } + + protected override void ForEachChild(Action childAction) + { + childAction(count); + childAction(array); + } + + public override string ToString() + { + return string.Join(",", this); + } + } +} diff --git a/Netcode/NetLong.cs b/Netcode/NetLong.cs new file mode 100644 index 0000000..bcce461 --- /dev/null +++ b/Netcode/NetLong.cs @@ -0,0 +1,53 @@ +using System.IO; + +namespace Netcode +{ + public sealed class NetLong : NetField + { + public NetLong() + { + } + + public NetLong(long value) + : base(value) + { + } + + public override void Set(long newValue) + { + if (canShortcutSet()) + { + value = newValue; + } + else if (newValue != value) + { + cleanSet(newValue); + MarkDirty(); + } + } + + protected override long interpolate(long startValue, long endValue, float factor) + { + return startValue + (long)((float)(endValue - startValue) * factor); + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + long newValue = reader.ReadInt64(); + if (version.IsPriorityOver(ChangeVersion)) + { + setInterpolationTarget(newValue); + } + } + + protected override void WriteDelta(BinaryWriter writer) + { + writer.Write(value); + } + + public static implicit operator long(NetLong field) + { + return field.value; + } + } +} diff --git a/Netcode/NetLongList.cs b/Netcode/NetLongList.cs new file mode 100644 index 0000000..e0fda95 --- /dev/null +++ b/Netcode/NetLongList.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; + +namespace Netcode +{ + public sealed class NetLongList : NetList + { + public NetLongList() + { + } + + public NetLongList(IEnumerable values) + : base(values) + { + } + + public NetLongList(int capacity) + : base(capacity) + { + } + + public override bool Contains(long item) + { + using (Enumerator enumerator = GetEnumerator()) + { + while (enumerator.MoveNext()) + { + if (enumerator.Current == item) + { + return true; + } + } + } + return false; + } + + public override int IndexOf(long item) + { + NetInt count = base.count; + for (int i = 0; i < (int)count; i++) + { + if (array.Value[i] == item) + { + return i; + } + } + return -1; + } + } +} diff --git a/Netcode/NetNullableEnum.cs b/Netcode/NetNullableEnum.cs new file mode 100644 index 0000000..3bc4da1 --- /dev/null +++ b/Netcode/NetNullableEnum.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Netcode +{ + public class NetNullableEnum : NetField>, IEnumerable, IEnumerable where T : struct, IConvertible + { + private bool xmlInitialized; + + public NetNullableEnum() + : base((T?)null) + { + } + + public NetNullableEnum(T value) + : base((T?)value) + { + } + + public override void Set(T? newValue) + { + if (!EqualityComparer.Default.Equals(newValue, value)) + { + cleanSet(newValue); + MarkDirty(); + } + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + T? newValue = null; + if (reader.ReadBoolean()) + { + newValue = (T)Enum.ToObject(typeof(T), reader.ReadInt16()); + } + if (version.IsPriorityOver(ChangeVersion)) + { + setInterpolationTarget(newValue); + } + } + + protected override void WriteDelta(BinaryWriter writer) + { + if (!value.HasValue) + { + writer.Write(value: false); + return; + } + writer.Write(value: true); + writer.Write(Convert.ToInt16(value)); + } + + public static implicit operator int(NetNullableEnum netField) + { + return Convert.ToInt32(netField.Get()); + } + + public static implicit operator short(NetNullableEnum netField) + { + return Convert.ToInt16(netField.Get()); + } + + public new IEnumerator GetEnumerator() + { + T? value = Get(); + if (!value.HasValue) + { + return Enumerable.Repeat(null, 1).GetEnumerator(); + } + return Enumerable.Repeat(Convert.ToString(value), 1).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(string value) + { + if (xmlInitialized || base.Parent != null) + { + throw new InvalidOperationException(GetType().Name + " already has value " + ToString()); + } + if (value != null && value != "") + { + cleanSet((T)Enum.Parse(typeof(T), value)); + } + else + { + cleanSet(null); + } + xmlInitialized = true; + } + } +} diff --git a/Netcode/NetObjectArray.cs b/Netcode/NetObjectArray.cs new file mode 100644 index 0000000..4c73896 --- /dev/null +++ b/Netcode/NetObjectArray.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace Netcode +{ + public class NetObjectArray : NetArray> where T : class, INetObject + { + public NetObjectArray() + { + } + + public NetObjectArray(IEnumerable values) + : base(values) + { + } + + public NetObjectArray(int size) + : base(size) + { + } + } +} diff --git a/Netcode/NetObjectList.cs b/Netcode/NetObjectList.cs new file mode 100644 index 0000000..b4032f9 --- /dev/null +++ b/Netcode/NetObjectList.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace Netcode +{ + public sealed class NetObjectList : NetList> where T : class, INetObject + { + public NetObjectList() + { + } + + public NetObjectList(IEnumerable values) + : base(values) + { + } + + public NetObjectList(int capacity) + : base(capacity) + { + } + } +} diff --git a/Netcode/NetObjectShrinkList.cs b/Netcode/NetObjectShrinkList.cs new file mode 100644 index 0000000..1f4fecd --- /dev/null +++ b/Netcode/NetObjectShrinkList.cs @@ -0,0 +1,388 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; + +namespace Netcode +{ + public class NetObjectShrinkList : AbstractNetSerializable, IList, ICollection, IEnumerable, IEnumerable, IEquatable> where T : class, INetObject + { + public struct Enumerator : IEnumerator, IDisposable, IEnumerator + { + private readonly NetArray> _array; + + private int _index; + + private T _current; + + private bool _done; + + public T Current => _current; + + object IEnumerator.Current + { + get + { + if (_done) + { + throw new InvalidOperationException(); + } + return _current; + } + } + + public Enumerator(NetArray> array) + { + _array = array; + _index = 0; + _current = null; + _done = false; + } + + public bool MoveNext() + { + while (_index < _array.Count) + { + T v = _array[_index]; + _index++; + if (v != null) + { + _current = v; + return true; + } + } + _done = true; + _current = null; + return false; + } + + public void Dispose() + { + } + + void IEnumerator.Reset() + { + _index = 0; + _current = null; + _done = false; + } + } + + private NetArray> array = new NetArray>(); + + public T this[int index] + { + get + { + int count = 0; + for (int i = 0; i < array.Count; i++) + { + T v = array[i]; + if (v != null) + { + if (index == count) + { + return v; + } + count++; + } + } + throw new ArgumentOutOfRangeException("index"); + } + set + { + int count = 0; + for (int i = 0; i < array.Count; i++) + { + if (array[i] != null) + { + if (index == count) + { + array[i] = value; + return; + } + count++; + } + } + throw new ArgumentOutOfRangeException("index"); + } + } + + public int Count + { + get + { + int count = 0; + for (int i = 0; i < array.Count; i++) + { + if (array[i] != null) + { + count++; + } + } + return count; + } + } + + public bool IsReadOnly => false; + + public NetObjectShrinkList() + { + } + + public NetObjectShrinkList(IEnumerable values) + : this() + { + foreach (T value in values) + { + array.Add(value); + } + } + + public void Add(T item) + { + array.Add(item); + } + + public void Clear() + { + for (int i = 0; i < array.Count; i++) + { + array[i] = null; + } + } + + public void CopyFrom(IList list) + { + if (list == this) + { + return; + } + if (list.Count > array.Count) + { + throw new InvalidOperationException(); + } + for (int i = 0; i < array.Count; i++) + { + if (i < list.Count) + { + array[i] = list[i]; + } + else + { + array[i] = null; + } + } + } + + public void Set(IList list) + { + CopyFrom(list); + } + + public void MoveFrom(IList list) + { + List values = new List(list); + list.Clear(); + Set(values); + } + + public bool Contains(T item) + { + using (Enumerator enumerator = GetEnumerator()) + { + while (enumerator.MoveNext()) + { + if (enumerator.Current == item) + { + return true; + } + } + } + return false; + } + + public void CopyTo(T[] array, int arrayIndex) + { + if (array == null) + { + throw new ArgumentNullException(); + } + if (arrayIndex < 0) + { + throw new ArgumentOutOfRangeException(); + } + if (Count - arrayIndex > array.Length) + { + throw new ArgumentException(); + } + using (Enumerator enumerator = GetEnumerator()) + { + while (enumerator.MoveNext()) + { + T value = enumerator.Current; + array[arrayIndex++] = value; + } + } + } + + public List GetRange(int index, int count) + { + List result = new List(); + for (int i = index; i < index + count; i++) + { + result.Add(this[i]); + } + return result; + } + + public void AddRange(IEnumerable collection) + { + foreach (T value in collection) + { + Add(value); + } + } + + public void RemoveRange(int index, int count) + { + for (int i = 0; i < count; i++) + { + RemoveAt(index); + } + } + + public bool Equals(NetObjectShrinkList other) + { + if (Count != other.Count) + { + return false; + } + for (int i = 0; i < Count; i++) + { + if (this[i] != other[i]) + { + return false; + } + } + return true; + } + + public Enumerator GetEnumerator() + { + return new Enumerator(array); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(array); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(array); + } + + public int IndexOf(T item) + { + int index = 0; + for (int i = 0; i < array.Count; i++) + { + T v = array[i]; + if (v != null) + { + if (v == item) + { + return index; + } + index++; + } + } + return -1; + } + + public void Insert(int index, T item) + { + int count = 0; + for (int i = 0; i < array.Count; i++) + { + if (array[i] != null) + { + if (count == index) + { + array.Insert(i, item); + return; + } + count++; + } + } + throw new ArgumentOutOfRangeException("index"); + } + + public override void Read(BinaryReader reader, NetVersion version) + { + array.Read(reader, version); + } + + public override void ReadFull(BinaryReader reader, NetVersion version) + { + array.ReadFull(reader, version); + } + + public bool Remove(T item) + { + for (int i = 0; i < array.Count; i++) + { + if (array[i] == item) + { + array[i] = null; + return true; + } + } + return false; + } + + public void RemoveAt(int index) + { + int count = 0; + int i = 0; + while (true) + { + if (i >= array.Count) + { + return; + } + if (array[i] != null) + { + if (count == index) + { + break; + } + count++; + } + i++; + } + array[i] = null; + } + + public override void Write(BinaryWriter writer) + { + array.Write(writer); + } + + public override void WriteFull(BinaryWriter writer) + { + array.WriteFull(writer); + } + + protected override void ForEachChild(Action childAction) + { + childAction(array); + } + + public override string ToString() + { + return string.Join(",", this); + } + } +} diff --git a/Netcode/NetPoint.cs b/Netcode/NetPoint.cs new file mode 100644 index 0000000..15f5e86 --- /dev/null +++ b/Netcode/NetPoint.cs @@ -0,0 +1,105 @@ +using Microsoft.Xna.Framework; +using System.IO; + +namespace Netcode +{ + public sealed class NetPoint : NetField + { + public int X + { + get + { + return base.Value.X; + } + set + { + Point point = base.value; + if (point.X != value) + { + Point newValue = new Point(value, point.Y); + if (canShortcutSet()) + { + base.value = newValue; + return; + } + cleanSet(newValue); + MarkDirty(); + } + } + } + + public int Y + { + get + { + return base.Value.Y; + } + set + { + Point point = base.value; + if (point.Y != value) + { + Point newValue = new Point(point.X, value); + if (canShortcutSet()) + { + base.value = newValue; + return; + } + cleanSet(newValue); + MarkDirty(); + } + } + } + + public NetPoint() + { + } + + public NetPoint(Point value) + : base(value) + { + } + + public void Set(int x, int y) + { + Set(new Point(x, y)); + } + + public override void Set(Point newValue) + { + if (canShortcutSet()) + { + value = newValue; + } + else if (newValue != value) + { + cleanSet(newValue); + MarkDirty(); + } + } + + protected override Point interpolate(Point startValue, Point endValue, float factor) + { + Point delta = new Point(endValue.X - startValue.X, endValue.Y - startValue.Y); + delta.X = (int)((float)delta.X * factor); + delta.Y = (int)((float)delta.Y * factor); + return new Point(startValue.X + delta.X, startValue.Y + delta.Y); + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + int newX = reader.ReadInt32(); + int newY = reader.ReadInt32(); + if (version.IsPriorityOver(ChangeVersion)) + { + setInterpolationTarget(new Point(newX, newY)); + } + } + + protected override void WriteDelta(BinaryWriter writer) + { + writer.Write(base.Value.X); + writer.Write(base.Value.Y); + } + } +} diff --git a/Netcode/NetRectangle.cs b/Netcode/NetRectangle.cs new file mode 100644 index 0000000..e85d46b --- /dev/null +++ b/Netcode/NetRectangle.cs @@ -0,0 +1,157 @@ +using Microsoft.Xna.Framework; +using System.IO; + +namespace Netcode +{ + public sealed class NetRectangle : NetField + { + public int X + { + get + { + return base.Value.X; + } + set + { + Rectangle rect = base.value; + if (rect.X != value) + { + Rectangle newValue = new Rectangle(value, rect.Y, rect.Width, rect.Height); + if (canShortcutSet()) + { + base.value = newValue; + return; + } + cleanSet(newValue); + MarkDirty(); + } + } + } + + public int Y + { + get + { + return base.Value.Y; + } + set + { + Rectangle rect = base.value; + if (rect.Y != value) + { + Rectangle newValue = new Rectangle(rect.X, value, rect.Width, rect.Height); + if (canShortcutSet()) + { + base.value = newValue; + return; + } + cleanSet(newValue); + MarkDirty(); + } + } + } + + public int Width + { + get + { + return base.Value.Width; + } + set + { + Rectangle rect = base.value; + if (rect.Width != value) + { + Rectangle newValue = new Rectangle(rect.X, rect.Y, value, rect.Height); + if (canShortcutSet()) + { + base.value = newValue; + return; + } + cleanSet(newValue); + MarkDirty(); + } + } + } + + public int Height + { + get + { + return base.Value.Height; + } + set + { + Rectangle rect = base.value; + if (rect.Height != value) + { + Rectangle newValue = new Rectangle(rect.X, rect.Y, rect.Width, value); + if (canShortcutSet()) + { + base.value = newValue; + return; + } + cleanSet(newValue); + MarkDirty(); + } + } + } + + public Point Center => value.Center; + + public int Top => value.Top; + + public int Bottom => value.Bottom; + + public int Left => value.Left; + + public int Right => value.Right; + + public NetRectangle() + { + } + + public NetRectangle(Rectangle value) + : base(value) + { + } + + public void Set(int x, int y, int width, int height) + { + Set(new Rectangle(x, y, width, height)); + } + + public override void Set(Rectangle newValue) + { + if (canShortcutSet()) + { + value = newValue; + } + else if (newValue != value) + { + cleanSet(newValue); + MarkDirty(); + } + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + int newX = reader.ReadInt32(); + int newY = reader.ReadInt32(); + int newWidth = reader.ReadInt32(); + int newHeight = reader.ReadInt32(); + if (version.IsPriorityOver(ChangeVersion)) + { + setInterpolationTarget(new Rectangle(newX, newY, newWidth, newHeight)); + } + } + + protected override void WriteDelta(BinaryWriter writer) + { + writer.Write(value.X); + writer.Write(value.Y); + writer.Write(value.Width); + writer.Write(value.Height); + } + } +} diff --git a/Netcode/NetRef.cs b/Netcode/NetRef.cs new file mode 100644 index 0000000..c246890 --- /dev/null +++ b/Netcode/NetRef.cs @@ -0,0 +1,14 @@ +namespace Netcode +{ + public class NetRef : NetExtendableRef> where T : class, INetObject + { + public NetRef() + { + } + + public NetRef(T value) + : base(value) + { + } + } +} diff --git a/Netcode/NetRefBase.cs b/Netcode/NetRefBase.cs new file mode 100644 index 0000000..fb2e2f1 --- /dev/null +++ b/Netcode/NetRefBase.cs @@ -0,0 +1,244 @@ +using System; +using System.IO; +using System.Xml.Serialization; + +namespace Netcode +{ + public abstract class NetRefBase : NetField where T : class where TSelf : NetRefBase + { + private enum RefDeltaType : byte + { + ChildDelta, + Reassigned + } + + public delegate void ConflictResolveEvent(T rejected, T accepted); + + public XmlSerializer Serializer; + + private RefDeltaType deltaType; + + protected NetVersion reassigned; + + public event ConflictResolveEvent OnConflictResolve; + + public NetRefBase() + { + } + + public NetRefBase(T value) + : this() + { + cleanSet(value); + } + + protected override void SetParent(INetSerializable parent) + { + if (parent == null || parent.Root != base.Root) + { + reassigned.Clear(); + } + base.SetParent(parent); + } + + protected override void CleanImpl() + { + base.CleanImpl(); + deltaType = RefDeltaType.ChildDelta; + } + + public void MarkReassigned() + { + deltaType = RefDeltaType.Reassigned; + if (base.Root != null) + { + reassigned.Set(base.Root.Clock.netVersion); + } + MarkDirty(); + } + + public override void Set(T newValue) + { + if (newValue != base.Value) + { + deltaType = RefDeltaType.Reassigned; + if (base.Root != null) + { + reassigned.Set(base.Root.Clock.netVersion); + } + cleanSet(newValue); + MarkDirty(); + } + } + + private T createType(Type type) + { + if (type == null) + { + return null; + } + return (T)Activator.CreateInstance(type); + } + + protected T ReadType(BinaryReader reader) + { + return createType(reader.ReadType()); + } + + protected void WriteType(BinaryWriter writer) + { + writer.WriteTypeOf(targetValue); + } + + private void serialize(BinaryWriter writer, XmlSerializer serializer = null) + { + using (MemoryStream stream = new MemoryStream()) + { + (serializer ?? Serializer).Serialize(stream, targetValue); + stream.Seek(0L, SeekOrigin.Begin); + writer.Write((int)stream.Length); + writer.Write(stream.ToArray()); + } + } + + private T deserialize(BinaryReader reader, XmlSerializer serializer = null) + { + int length = reader.ReadInt32(); + using (MemoryStream stream = new MemoryStream(reader.ReadBytes(length))) + { + return (T)(serializer ?? Serializer).Deserialize(stream); + } + } + + protected abstract void ReadValueFull(T value, BinaryReader reader, NetVersion version); + + protected abstract void ReadValueDelta(BinaryReader reader, NetVersion version); + + protected abstract void WriteValueFull(BinaryWriter writer); + + protected abstract void WriteValueDelta(BinaryWriter writer); + + private void writeBaseValue(BinaryWriter writer) + { + if (Serializer != null) + { + serialize(writer); + } + else + { + WriteType(writer); + } + } + + private T readBaseValue(BinaryReader reader, NetVersion version) + { + if (Serializer != null) + { + return deserialize(reader); + } + return ReadType(reader); + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + if (reader.ReadByte() == 1) + { + reader.ReadSkippable(delegate + { + NetVersion other = default(NetVersion); + other.Read(reader); + T val = readBaseValue(reader, version); + if (val != null) + { + ReadValueFull(val, reader, version); + } + if (other.IsIndependent(reassigned)) + { + if (!other.IsPriorityOver(reassigned)) + { + if (this.OnConflictResolve != null) + { + this.OnConflictResolve(val, targetValue); + } + return; + } + if (this.OnConflictResolve != null) + { + this.OnConflictResolve(targetValue, val); + } + } + else if (!other.IsPriorityOver(reassigned)) + { + return; + } + reassigned.Set(other); + setInterpolationTarget(val); + }); + } + else + { + reader.ReadSkippable(delegate + { + if (version.IsPrecededBy(reassigned) && targetValue != null) + { + ReadValueDelta(reader, version); + } + }); + } + } + + protected override void WriteDelta(BinaryWriter writer) + { + writer.Push((targetValue != null) ? targetValue.GetType().Name : "null"); + writer.Write((byte)deltaType); + if (deltaType == RefDeltaType.Reassigned) + { + writer.WriteSkippable(delegate + { + reassigned.Write(writer); + writeBaseValue(writer); + if (targetValue != null) + { + WriteValueFull(writer); + } + }); + } + else + { + writer.WriteSkippable(delegate + { + if (targetValue != null) + { + WriteValueDelta(writer); + } + }); + } + deltaType = RefDeltaType.ChildDelta; + writer.Pop(); + } + + public override void ReadFull(BinaryReader reader, NetVersion version) + { + reassigned.Read(reader); + T remoteValue = readBaseValue(reader, version); + if (remoteValue != null) + { + ReadValueFull(remoteValue, reader, version); + } + cleanSet(remoteValue); + ChangeVersion.Merge(version); + } + + public override void WriteFull(BinaryWriter writer) + { + writer.Push((targetValue != null) ? targetValue.GetType().Name : "null"); + reassigned.Write(writer); + writeBaseValue(writer); + if (targetValue != null) + { + WriteValueFull(writer); + } + writer.Pop(); + } + } +} diff --git a/Netcode/NetRefTypes.cs b/Netcode/NetRefTypes.cs new file mode 100644 index 0000000..1d5537f --- /dev/null +++ b/Netcode/NetRefTypes.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +namespace Netcode +{ + internal static class NetRefTypes + { + private static Dictionary types = new Dictionary(); + + public static Type ReadType(this BinaryReader reader) + { + Type genericType = reader.ReadGenericType(); + if (genericType == null || !genericType.IsGenericTypeDefinition) + { + return genericType; + } + int numArgs = genericType.GetGenericArguments().Length; + Type[] arguments = new Type[numArgs]; + for (int i = 0; i < numArgs; i++) + { + arguments[i] = reader.ReadType(); + } + return genericType.MakeGenericType(arguments); + } + + private static Type ReadGenericType(this BinaryReader reader) + { + string typeName = reader.ReadString(); + if (typeName.Length == 0) + { + return null; + } + Type type = GetType(typeName); + if (type == null) + { + throw new InvalidOperationException(); + } + return type; + } + + public static void WriteType(this BinaryWriter writer, Type type) + { + Type genericType = type; + if (type != null && type.IsGenericType) + { + genericType = type.GetGenericTypeDefinition(); + } + writer.WriteGenericType(genericType); + if (!(genericType == null) && genericType.IsGenericType) + { + Type[] genericArguments = type.GetGenericArguments(); + foreach (Type argument in genericArguments) + { + writer.WriteType(argument); + } + } + } + + private static void WriteGenericType(this BinaryWriter writer, Type type) + { + if (type == null) + { + writer.Write(""); + } + else + { + writer.Write(type.FullName); + } + } + + public static void WriteTypeOf(this BinaryWriter writer, T value) + { + if (value == null) + { + writer.WriteType(null); + } + else + { + writer.WriteType(value.GetType()); + } + } + + private static Type GetType(string typeName) + { + if (types.TryGetValue(typeName, out Type type2)) + { + return type2; + } + Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + for (int i = 0; i < assemblies.Length; i++) + { + type2 = assemblies[i].GetType(typeName); + if (type2 != null) + { + types[typeName] = type2; + return type2; + } + } + return null; + } + } +} diff --git a/Netcode/NetRoot.cs b/Netcode/NetRoot.cs new file mode 100644 index 0000000..ef1b052 --- /dev/null +++ b/Netcode/NetRoot.cs @@ -0,0 +1,134 @@ +using System.Collections.Generic; +using System.IO; + +namespace Netcode +{ + public class NetRoot : NetRef, INetRoot where T : class, INetObject + { + private Dictionary connections = new Dictionary(); + + public NetClock Clock + { + get; + } = new NetClock(); + + + public override bool Dirty => base.DirtyTick <= Clock.GetLocalTick(); + + public NetRoot() + { + base.Root = this; + } + + public NetRoot(T value) + : this() + { + cleanSet(value); + } + + public void TickTree() + { + Clock.Tick(); + Tick(); + } + + public override void Read(BinaryReader reader, NetVersion _) + { + NetVersion remoteVersion = default(NetVersion); + remoteVersion.Read(reader); + base.Read(reader, remoteVersion); + Clock.netVersion.Merge(remoteVersion); + } + + public void Read(BinaryReader reader) + { + NetVersion remoteVersion = default(NetVersion); + remoteVersion.Read(reader); + base.Read(reader, remoteVersion); + Clock.netVersion.Merge(remoteVersion); + } + + public override void Write(BinaryWriter writer) + { + Clock.netVersion.Write(writer); + base.Write(writer); + MarkClean(); + } + + public override void ReadFull(BinaryReader reader, NetVersion _) + { + base.ReadFull(reader, Clock.netVersion); + } + + public static NetRoot Connect(BinaryReader reader) + { + NetRoot netRoot = new NetRoot(); + netRoot.ReadConnectionPacket(reader); + return netRoot; + } + + public void ReadConnectionPacket(BinaryReader reader) + { + Clock.LocalId = reader.ReadByte(); + Clock.netVersion.Read(reader); + base.ReadFull(reader, Clock.netVersion); + } + + public void CreateConnectionPacket(BinaryWriter writer, long? connection) + { + int peerId; + if (connection.HasValue && connections.ContainsKey(connection.Value)) + { + peerId = connections[connection.Value]; + } + else + { + peerId = Clock.AddNewPeer(); + if (connection.HasValue) + { + connections[connection.Value] = peerId; + } + } + writer.Write((byte)peerId); + Clock.netVersion.Write(writer); + WriteFull(writer); + } + + public void Disconnect(long connection) + { + if (connections.TryGetValue(connection, out int peerId)) + { + Clock.RemovePeer(peerId); + } + } + + public virtual NetRoot Clone() + { + using (MemoryStream stream = new MemoryStream()) + { + using (BinaryWriter writer = new BinaryWriter(stream)) + { + using (BinaryReader reader = new BinaryReader(stream)) + { + WriteFull(writer); + stream.Seek(0L, SeekOrigin.Begin); + NetRoot netRoot = new NetRoot(); + netRoot.Serializer = Serializer; + netRoot.ReadFull(reader, Clock.netVersion); + netRoot.reassigned.Set(default(NetVersion)); + netRoot.MarkClean(); + return netRoot; + } + } + } + } + + public void CloneInto(NetRef netref) + { + NetRoot netRoot = Clone(); + T copy = netRoot.Value; + netRoot.Value = null; + netref.Value = copy; + } + } +} diff --git a/Netcode/NetRootDictionary.cs b/Netcode/NetRootDictionary.cs new file mode 100644 index 0000000..8642894 --- /dev/null +++ b/Netcode/NetRootDictionary.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Serialization; + +namespace Netcode +{ + public class NetRootDictionary : IDictionary, ICollection>, IEnumerable>, IEnumerable where TValue : class, INetObject + { + public struct Enumerator : IEnumerator>, IDisposable, IEnumerator + { + private Dictionary> _roots; + + private Dictionary>.Enumerator _enumerator; + + private KeyValuePair _current; + + private bool _done; + + public KeyValuePair Current => _current; + + object IEnumerator.Current + { + get + { + if (_done) + { + throw new InvalidOperationException(); + } + return _current; + } + } + + public Enumerator(Dictionary> roots) + { + _roots = roots; + _enumerator = _roots.GetEnumerator(); + _current = default(KeyValuePair); + _done = false; + } + + public bool MoveNext() + { + if (_enumerator.MoveNext()) + { + KeyValuePair> pair = _enumerator.Current; + _current = new KeyValuePair(pair.Key, pair.Value.Get()); + return true; + } + _done = true; + _current = default(KeyValuePair); + return false; + } + + public void Dispose() + { + } + + void IEnumerator.Reset() + { + _enumerator = _roots.GetEnumerator(); + _current = default(KeyValuePair); + _done = false; + } + } + + public XmlSerializer Serializer; + + public Dictionary> Roots = new Dictionary>(); + + public TValue this[TKey key] + { + get + { + return Roots[key].Get(); + } + set + { + if (!ContainsKey(key)) + { + Add(key, value); + } + else + { + Roots[key].Set(value); + } + } + } + + public int Count => Roots.Count; + + public bool IsReadOnly => ((IDictionary)Roots).IsReadOnly; + + public ICollection Keys => Roots.Keys; + + public ICollection Values => Roots.Values.Select((NetRoot root) => root.Get()).ToList(); + + public NetRootDictionary() + { + } + + public NetRootDictionary(IEnumerable> values) + { + foreach (KeyValuePair pair in values) + { + Add(pair.Key, pair.Value); + } + } + + public void Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } + + public void Add(TKey key, TValue value) + { + NetRoot root = new NetRoot(value); + root.Serializer = Serializer; + Roots.Add(key, root); + } + + public void Clear() + { + Roots.Clear(); + } + + public bool Contains(KeyValuePair item) + { + if (!Roots.ContainsKey(item.Key)) + { + return false; + } + return Roots[item.Key] == item.Value; + } + + public bool ContainsKey(TKey key) + { + return Roots.ContainsKey(key); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (array == null) + { + throw new ArgumentNullException(); + } + if (arrayIndex < 0) + { + throw new ArgumentOutOfRangeException(); + } + if (array.Length < Count - arrayIndex) + { + throw new ArgumentException(); + } + using (Enumerator enumerator = GetEnumerator()) + { + while (enumerator.MoveNext()) + { + KeyValuePair pair = enumerator.Current; + array[arrayIndex++] = pair; + } + } + } + + public Enumerator GetEnumerator() + { + return new Enumerator(Roots); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return new Enumerator(Roots); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(Roots); + } + + public bool Remove(KeyValuePair item) + { + if (Contains(item)) + { + return Remove(item.Key); + } + return false; + } + + public bool Remove(TKey key) + { + return Roots.Remove(key); + } + + public bool TryGetValue(TKey key, out TValue value) + { + if (Roots.TryGetValue(key, out NetRoot root)) + { + value = root.Get(); + return true; + } + value = null; + return false; + } + } +} diff --git a/Netcode/NetRotation.cs b/Netcode/NetRotation.cs new file mode 100644 index 0000000..b6eebf8 --- /dev/null +++ b/Netcode/NetRotation.cs @@ -0,0 +1,62 @@ +using System; +using System.IO; + +namespace Netcode +{ + public class NetRotation : NetField + { + public NetRotation() + { + } + + public NetRotation(float value) + : base(value) + { + } + + public override void Set(float newValue) + { + if (canShortcutSet()) + { + value = newValue; + } + else if (newValue != value) + { + cleanSet(newValue); + MarkDirty(); + } + } + + protected override float interpolate(float startValue, float endValue, float factor) + { + float num = Math.Abs(endValue - startValue); + float period = (float)Math.PI * 2f; + if (num > 180f) + { + if (endValue > startValue) + { + startValue += period; + } + else + { + endValue += period; + } + } + return (startValue + (endValue - startValue) * factor) % period; + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + float newValue = reader.ReadSingle(); + if (version.IsPriorityOver(ChangeVersion)) + { + setInterpolationTarget(newValue); + } + } + + protected override void WriteDelta(BinaryWriter writer) + { + writer.Write(value); + } + } +} diff --git a/Netcode/NetString.cs b/Netcode/NetString.cs new file mode 100644 index 0000000..ea40d37 --- /dev/null +++ b/Netcode/NetString.cs @@ -0,0 +1,71 @@ +using System.IO; + +namespace Netcode +{ + public sealed class NetString : NetField + { + public delegate string FilterString(string newValue); + + public int Length => base.Value.Length; + + public event FilterString FilterStringEvent; + + public NetString() + : base((string)null) + { + } + + public NetString(string value) + : base(value) + { + } + + public override void Set(string newValue) + { + if (canShortcutSet()) + { + value = newValue; + } + else if (newValue != value) + { + cleanSet(newValue); + MarkDirty(); + } + } + + public bool Contains(string substr) + { + if (base.Value != null) + { + return base.Value.Contains(substr); + } + return false; + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + string newValue = null; + if (reader.ReadBoolean()) + { + newValue = reader.ReadString(); + if (this.FilterStringEvent != null) + { + newValue = this.FilterStringEvent(newValue); + } + } + if (version.IsPriorityOver(ChangeVersion)) + { + setInterpolationTarget(newValue); + } + } + + protected override void WriteDelta(BinaryWriter writer) + { + writer.Write(value != null); + if (value != null) + { + writer.Write(value); + } + } + } +} diff --git a/Netcode/NetStringList.cs b/Netcode/NetStringList.cs new file mode 100644 index 0000000..dcc787c --- /dev/null +++ b/Netcode/NetStringList.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace Netcode +{ + public sealed class NetStringList : NetList + { + public NetStringList() + { + } + + public NetStringList(IEnumerable values) + : base(values) + { + } + + public NetStringList(int capacity) + : base(capacity) + { + } + } +} diff --git a/Netcode/NetTimestamp.cs b/Netcode/NetTimestamp.cs new file mode 100644 index 0000000..781b571 --- /dev/null +++ b/Netcode/NetTimestamp.cs @@ -0,0 +1,28 @@ +using System.IO; + +namespace Netcode +{ + public struct NetTimestamp + { + public int PeerId; + + public uint Tick; + + public void Read(BinaryReader reader) + { + PeerId = reader.ReadByte(); + Tick = reader.ReadUInt32(); + } + + public void Write(BinaryWriter writer) + { + writer.Write((byte)PeerId); + writer.Write(Tick); + } + + public override string ToString() + { + return "v" + Tick + "@" + PeerId; + } + } +} diff --git a/Netcode/NetVector2.cs b/Netcode/NetVector2.cs new file mode 100644 index 0000000..744b458 --- /dev/null +++ b/Netcode/NetVector2.cs @@ -0,0 +1,256 @@ +using Microsoft.Xna.Framework; +using System; +using System.IO; + +namespace Netcode +{ + public sealed class NetVector2 : NetField + { + public bool AxisAlignedMovement; + + public float ExtrapolationSpeed; + + public float MinDeltaForDirectionChange = 8f; + + public float MaxInterpolationDistance = 320f; + + private bool interpolateXFirst; + + private bool isExtrapolating; + + private bool isFixingExtrapolation; + + public float X + { + get + { + return base.Value.X; + } + set + { + Vector2 vector = base.value; + if (vector.X != value) + { + Vector2 newValue = new Vector2(value, vector.Y); + if (canShortcutSet()) + { + base.value = newValue; + return; + } + cleanSet(newValue); + MarkDirty(); + } + } + } + + public float Y + { + get + { + return base.Value.Y; + } + set + { + Vector2 vector = base.value; + if (vector.Y != value) + { + Vector2 newValue = new Vector2(vector.X, value); + if (canShortcutSet()) + { + base.value = newValue; + return; + } + cleanSet(newValue); + MarkDirty(); + } + } + } + + public NetVector2() + { + } + + public NetVector2(Vector2 value) + : base(value) + { + } + + public void Set(float x, float y) + { + Set(new Vector2(x, y)); + } + + public override void Set(Vector2 newValue) + { + if (canShortcutSet()) + { + value = newValue; + } + else if (newValue != value) + { + cleanSet(newValue); + MarkDirty(); + } + } + + public Vector2 InterpolationDelta() + { + if (base.NeedsTick) + { + return targetValue - previousValue; + } + return Vector2.Zero; + } + + protected override bool setUpInterpolation(Vector2 oldValue, Vector2 newValue) + { + if ((newValue - oldValue).LengthSquared() >= MaxInterpolationDistance * MaxInterpolationDistance) + { + return false; + } + if (AxisAlignedMovement) + { + if (base.NeedsTick) + { + Vector2 delta2 = targetValue - previousValue; + Vector2 absDelta2 = new Vector2(Math.Abs(delta2.X), Math.Abs(delta2.Y)); + if (interpolateXFirst) + { + interpolateXFirst = (InterpolationFactor() * (absDelta2.X + absDelta2.Y) < absDelta2.X); + } + else + { + interpolateXFirst = (InterpolationFactor() * (absDelta2.X + absDelta2.Y) > absDelta2.Y); + } + } + else + { + Vector2 delta = newValue - oldValue; + Vector2 absDelta = new Vector2(Math.Abs(delta.X), Math.Abs(delta.Y)); + interpolateXFirst = (absDelta.X < absDelta.Y); + } + } + return true; + } + + public Vector2 CurrentInterpolationDirection() + { + if (AxisAlignedMovement) + { + float factor = InterpolationFactor(); + Vector2 delta2 = InterpolationDelta(); + float traveledLength = (Math.Abs(delta2.X) + Math.Abs(delta2.Y)) * factor; + if (Math.Abs(delta2.X) < MinDeltaForDirectionChange && Math.Abs(delta2.Y) < MinDeltaForDirectionChange) + { + return Vector2.Zero; + } + if (Math.Abs(delta2.X) < MinDeltaForDirectionChange) + { + return new Vector2(0f, Math.Sign(delta2.Y)); + } + if (Math.Abs(delta2.Y) < MinDeltaForDirectionChange) + { + return new Vector2(Math.Sign(delta2.X), 0f); + } + if (interpolateXFirst) + { + if (traveledLength > Math.Abs(delta2.X)) + { + return new Vector2(0f, Math.Sign(delta2.Y)); + } + return new Vector2(Math.Sign(delta2.X), 0f); + } + if (traveledLength > Math.Abs(delta2.Y)) + { + return new Vector2(Math.Sign(delta2.X), 0f); + } + return new Vector2(0f, Math.Sign(delta2.Y)); + } + Vector2 delta = InterpolationDelta(); + delta.Normalize(); + return delta; + } + + public float CurrentInterpolationSpeed() + { + float distance = InterpolationDelta().Length(); + if (InterpolationTicks() == 0) + { + return distance; + } + if (InterpolationFactor() > 1f) + { + return ExtrapolationSpeed; + } + return distance / (float)InterpolationTicks(); + } + + protected override Vector2 interpolate(Vector2 startValue, Vector2 endValue, float factor) + { + if (AxisAlignedMovement && factor <= 1f && !isFixingExtrapolation) + { + isExtrapolating = false; + Vector2 delta = InterpolationDelta(); + Vector2 absDelta = new Vector2(Math.Abs(delta.X), Math.Abs(delta.Y)); + float traveledLength = (absDelta.X + absDelta.Y) * factor; + float x2 = startValue.X; + float y2 = startValue.Y; + if (interpolateXFirst) + { + if (traveledLength > absDelta.X) + { + x2 = endValue.X; + y2 = startValue.Y + (traveledLength - absDelta.X) * (float)Math.Sign(delta.Y); + } + else + { + x2 = startValue.X + traveledLength * (float)Math.Sign(delta.X); + y2 = startValue.Y; + } + } + else if (traveledLength > absDelta.Y) + { + y2 = endValue.Y; + x2 = startValue.X + (traveledLength - absDelta.Y) * (float)Math.Sign(delta.X); + } + else + { + y2 = startValue.Y + traveledLength * (float)Math.Sign(delta.Y); + x2 = startValue.X; + } + return new Vector2(x2, y2); + } + if (factor > 1f) + { + isExtrapolating = true; + uint extrapolationTicks = (uint)((int)(base.Root.Clock.GetLocalTick() - interpolationStartTick) - InterpolationTicks()); + Vector2 direction = endValue - startValue; + if (direction.LengthSquared() > ExtrapolationSpeed * ExtrapolationSpeed) + { + direction.Normalize(); + return endValue + direction * (float)(double)extrapolationTicks * ExtrapolationSpeed; + } + } + isExtrapolating = false; + return startValue + (endValue - startValue) * factor; + } + + protected override void ReadDelta(BinaryReader reader, NetVersion version) + { + float newX = reader.ReadSingle(); + float newY = reader.ReadSingle(); + if (version.IsPriorityOver(ChangeVersion)) + { + isFixingExtrapolation = isExtrapolating; + setInterpolationTarget(new Vector2(newX, newY)); + isExtrapolating = false; + } + } + + protected override void WriteDelta(BinaryWriter writer) + { + writer.Write(base.Value.X); + writer.Write(base.Value.Y); + } + } +} diff --git a/Netcode/NetVersion.cs b/Netcode/NetVersion.cs new file mode 100644 index 0000000..6d98131 --- /dev/null +++ b/Netcode/NetVersion.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Netcode +{ + public struct NetVersion : IEquatable + { + private List _vector; + + private List vector + { + get + { + if (_vector == null) + { + _vector = new List(); + } + return _vector; + } + } + + public uint this[int peerId] + { + get + { + if (peerId >= vector.Count) + { + return 0u; + } + return vector[peerId]; + } + set + { + while (vector.Count <= peerId) + { + vector.Add(0u); + } + vector[peerId] = value; + } + } + + public NetVersion(NetVersion other) + { + _vector = new List(); + Set(other); + } + + public NetTimestamp GetTimestamp(int peerId) + { + NetTimestamp result = default(NetTimestamp); + result.PeerId = peerId; + result.Tick = this[peerId]; + return result; + } + + public int Size() + { + return vector.Count; + } + + public void Set(NetVersion other) + { + for (int i = 0; i < Math.Max(Size(), other.Size()); i++) + { + this[i] = other[i]; + } + } + + public void Merge(NetVersion other) + { + for (int i = 0; i < Math.Max(Size(), other.Size()); i++) + { + this[i] = Math.Max(this[i], other[i]); + } + } + + public bool IsPriorityOver(NetVersion other) + { + for (int i = 0; i < Math.Max(Size(), other.Size()); i++) + { + if (this[i] > other[i]) + { + return true; + } + if (this[i] < other[i]) + { + return false; + } + } + return true; + } + + public bool IsSimultaneousWith(NetVersion other) + { + return isOrdered(other, (uint a, uint b) => a == b); + } + + public bool IsPrecededBy(NetVersion other) + { + return isOrdered(other, (uint a, uint b) => a >= b); + } + + public bool IsFollowedBy(NetVersion other) + { + return isOrdered(other, (uint a, uint b) => a < b); + } + + public bool IsIndependent(NetVersion other) + { + if (!IsSimultaneousWith(other) && !IsPrecededBy(other)) + { + return !IsFollowedBy(other); + } + return false; + } + + private bool isOrdered(NetVersion other, Func comparison) + { + for (int i = 0; i < Math.Max(Size(), other.Size()); i++) + { + if (!comparison(this[i], other[i])) + { + return false; + } + } + return true; + } + + public override string ToString() + { + if (Size() == 0) + { + return "v0"; + } + return "v" + string.Join(",", vector); + } + + public bool Equals(NetVersion other) + { + for (int i = 0; i < Math.Max(Size(), other.Size()); i++) + { + if (this[i] != other[i]) + { + return false; + } + } + return true; + } + + public override int GetHashCode() + { + return vector.GetHashCode() ^ -583558975; + } + + public void Write(BinaryWriter writer) + { + writer.Write((byte)Size()); + for (int i = 0; i < Size(); i++) + { + writer.Write(this[i]); + } + } + + public void Read(BinaryReader reader) + { + int size = reader.ReadByte(); + while (vector.Count > size) + { + vector.RemoveAt(size); + } + while (vector.Count < size) + { + vector.Add(0u); + } + for (int j = 0; j < size; j++) + { + this[j] = reader.ReadUInt32(); + } + for (int i = size; i < Size(); i++) + { + this[i] = 0u; + } + } + + public void Clear() + { + for (int i = 0; i < Size(); i++) + { + this[i] = 0u; + } + } + } +} diff --git a/Netcode/Netcode.csproj b/Netcode/Netcode.csproj new file mode 100644 index 0000000..efc2a63 --- /dev/null +++ b/Netcode/Netcode.csproj @@ -0,0 +1,33 @@ + + + + + net40 + x86 + + + + True + Netcode + false + Library + + + + DEBUG;TRACE + false + full + true + + + + TRACE + true + + + + + ..\lib\Microsoft.Xna.Framework.dll + + + \ No newline at end of file diff --git a/Netcode/Properties/AssemblyInfo.cs b/Netcode/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..caaacc9 --- /dev/null +++ b/Netcode/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +[assembly: CompilationRelaxations(8)] +[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] +[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] +[assembly: AssemblyTitle("Netcode")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Netcode")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: ComVisible(false)] +[assembly: Guid("b083bb02-f43e-4882-a5e8-96cec859e46c")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("1.0.0.0")] diff --git a/Netcode/SerializationCollectionFacade.cs b/Netcode/SerializationCollectionFacade.cs new file mode 100644 index 0000000..88a940c --- /dev/null +++ b/Netcode/SerializationCollectionFacade.cs @@ -0,0 +1,31 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Netcode +{ + public abstract class SerializationCollectionFacade : IEnumerable, IEnumerable + { + public SerializationCollectionFacade() + { + } + + protected abstract List Serialize(); + + protected abstract void DeserializeAdd(SerialT serialElem); + + public IEnumerator GetEnumerator() + { + return Serialize().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(SerialT value) + { + DeserializeAdd(value); + } + } +} diff --git a/Netcode/SerializationFacade.cs b/Netcode/SerializationFacade.cs new file mode 100644 index 0000000..355d57a --- /dev/null +++ b/Netcode/SerializationFacade.cs @@ -0,0 +1,32 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Netcode +{ + public abstract class SerializationFacade : IEnumerable, IEnumerable + { + public SerializationFacade() + { + } + + protected abstract SerialT Serialize(); + + protected abstract void Deserialize(SerialT serialValue); + + public IEnumerator GetEnumerator() + { + return Enumerable.Repeat(Serialize(), 1).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(SerialT value) + { + Deserialize(value); + } + } +} diff --git a/Netcode/StardewValley.Util/GuidHelper.cs b/Netcode/StardewValley.Util/GuidHelper.cs new file mode 100644 index 0000000..fe0288a --- /dev/null +++ b/Netcode/StardewValley.Util/GuidHelper.cs @@ -0,0 +1,12 @@ +using System; + +namespace StardewValley.Util +{ + internal static class GuidHelper + { + public static Guid NewGuid() + { + return Guid.NewGuid(); + } + } +} diff --git a/StardewValley.sln b/StardewValley.sln index 0e8248f..6d583b2 100644 --- a/StardewValley.sln +++ b/StardewValley.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StardewValley", "StardewVal EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StardewValley.GameData", "StardewValley.GameData\StardewValley.GameData.csproj", "{37282048-0EAF-4DE3-A7C8-0DEF3FECB704}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Netcode", "Netcode\Netcode.csproj", "{4EC221F1-FC32-4025-9A8B-A746B780A098}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x86 = Debug|x86 @@ -21,6 +23,10 @@ Global {37282048-0EAF-4DE3-A7C8-0DEF3FECB704}.Debug|x86.Build.0 = Debug|x86 {37282048-0EAF-4DE3-A7C8-0DEF3FECB704}.Release|x86.ActiveCfg = Release|x86 {37282048-0EAF-4DE3-A7C8-0DEF3FECB704}.Release|x86.Build.0 = Release|x86 + {4EC221F1-FC32-4025-9A8B-A746B780A098}.Debug|x86.ActiveCfg = Debug|x86 + {4EC221F1-FC32-4025-9A8B-A746B780A098}.Debug|x86.Build.0 = Debug|x86 + {4EC221F1-FC32-4025-9A8B-A746B780A098}.Release|x86.ActiveCfg = Release|x86 + {4EC221F1-FC32-4025-9A8B-A746B780A098}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/StardewValley/StardewValley.csproj b/StardewValley/StardewValley.csproj index bd0bb22..075338b 100644 --- a/StardewValley/StardewValley.csproj +++ b/StardewValley/StardewValley.csproj @@ -27,6 +27,7 @@ + @@ -57,9 +58,6 @@ ..\lib\Steamworks.NET.dll - - ..\lib\Netcode.dll - ..\lib\xTile.dll