Add basic syncing. Has no actual effect yet.

This commit is contained in:
Sofia 2017-05-09 17:15:21 +03:00
parent cbe7769e2b
commit 1b32c0c550
16 changed files with 521 additions and 64 deletions

View File

@ -1,4 +1,6 @@
using UnityEngine;
using System;
using UnityEngine;
using UnityEngine.Networking;
namespace Cyber.Entities {
@ -75,6 +77,35 @@ namespace Cyber.Entities {
transform.localEulerAngles.y, Head.localEulerAngles.z);
}
/// <summary>
/// Gets the Sync Handletype for Character, which doesn't require hash differences and syncs every tick.
/// </summary>
/// <returns></returns>
public override SyncHandletype GetSyncHandletype() {
return new SyncHandletype(false, 1);
}
/// <summary>
/// Deserializes the character.
/// </summary>
/// <param name="reader"></param>
public override void Deserialize(NetworkReader reader) {
Vector3 pos = reader.ReadVector3();
transform.position.Set(pos.x, pos.y, pos.z);
Move(reader.ReadVector3());
Vector3 rot = reader.ReadVector3();
}
/// <summary>
/// Serializes the character.
/// </summary>
/// <param name="writer"></param>
public override void Serialize(NetworkWriter writer) {
writer.Write(transform.position);
writer.Write(MovementDirection);
writer.Write(GetRotation());
}
private void FixedUpdate() {
CharacterController.Move(MovementDirection * MovementSpeed * Time.fixedDeltaTime);
}

View File

@ -1,6 +1,5 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine;
using UnityEngine.Networking;
namespace Cyber.Entities {
@ -8,12 +7,38 @@ namespace Cyber.Entities {
/// A base class for all syncable components. An instance of
/// <see cref="SyncDB"/> will contain all of the game's synced components.
/// </summary>
public class SyncBase : MonoBehaviour {
public abstract class SyncBase : MonoBehaviour {
/// <summary>
/// The ID this syncable component can be found with from its parent
/// <see cref="SyncDB"/>.
/// </summary>
public int ID;
/// <summary>
/// Return the Sync Handletype information for <see cref="Syncer"/>.
/// </summary>
/// <returns>Sync Handletype containing sync information.</returns>
public abstract SyncHandletype GetSyncHandletype();
/// <summary>
/// Deserializes this SyncBase for further use.
/// </summary>
/// <param name="reader"></param>
public abstract void Deserialize(NetworkReader reader);
/// <summary>
/// Serialize this SyncBase into a sync packet.
/// </summary>
/// <param name="writer"></param>
public abstract void Serialize(NetworkWriter writer);
/// <summary>
/// Generates a checksum of the contents, or 0 if no checksum is overriden.
/// </summary>
/// <returns>The integer checksum.</returns>
public virtual int GenerateChecksum() {
return 0;
}
}
}

View File

@ -1,7 +1,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.Generic;
using System;
using UnityEngine;
using Cyber.Networking.Serverside;
namespace Cyber.Entities {
@ -17,6 +17,8 @@ namespace Cyber.Entities {
private int IDCounter = 0;
private Dictionary<int, SyncBase> Database = new Dictionary<int, SyncBase>();
private Dictionary<Type, List<int>> CategorizedDatabase = new Dictionary<Type, List<int>>();
private Dictionary<Type, SyncHandletype> SyncHandletypes = new Dictionary<Type, SyncHandletype>();
/// <summary>
/// Add an entity to the database with the given IDs.
@ -31,6 +33,14 @@ namespace Cyber.Entities {
if (Syncable != null) {
Syncable.ID = ids[Index];
Database[ids[Index++]] = Syncable;
if (Server.IsRunning()) {
Type Type = Syncable.GetType();
if (!CategorizedDatabase.ContainsKey(Type)) {
CategorizedDatabase.Add(Type, new List<int>());
SyncHandletypes.Add(Type, Syncable.GetSyncHandletype());
}
CategorizedDatabase[Type].Add(Syncable.ID);
}
}
}
}
@ -83,6 +93,22 @@ namespace Cyber.Entities {
return Database[id];
}
/// <summary>
/// Gives the database categorized into lists of their types.
/// </summary>
/// <returns>A dictionary of categorized SyncBases.</returns>
public Dictionary<Type, List<int>> GetCategorizedDatabase() {
return CategorizedDatabase;
}
/// <summary>
/// Gets the Sync Handletypes currently known by the SyncDB.
/// </summary>
/// <returns>The Sync Handletypes by Type.</returns>
public Dictionary<Type, SyncHandletype> GetSyncHandletypes() {
return SyncHandletypes;
}
/// <summary>
/// Creates a new ID which isn't in use yet.
/// </summary>

View File

@ -0,0 +1,33 @@

namespace Cyber.Entities {
/// <summary>
/// A struct that gives the following information to Syncer about a SyncBase type:
/// Does it require a hash difference when syncing?
/// How often should this sync be done?
/// </summary>
public struct SyncHandletype {
/// <summary>
/// Weather hash difference should be checked before this sync can be done.
/// If true, will override <see cref="TickInterval"/>.
/// </summary>
public bool RequireHash;
/// <summary>
/// How often in ticks should the syncer sync this.
/// </summary>
public int TickInterval;
/// <summary>
/// A one-liner to create this struct.
/// </summary>
/// <param name="requireHash">Weather the SyncBase requires a hash to be synced.</param>
/// <param name="tickInterval">How often should this be synced.</param>
public SyncHandletype(bool requireHash, int tickInterval) {
RequireHash = requireHash;
TickInterval = tickInterval;
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: df50627e354741e4295b8da5ba13aa29
timeCreated: 1494329035
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -27,6 +27,8 @@ namespace Cyber.Networking.Clientside {
private Spawner Spawner;
private SyncHandler SyncHandler;
/// <summary>
/// The player of this client
/// </summary>
@ -92,6 +94,7 @@ namespace Cyber.Networking.Clientside {
private void Start() {
Spawner = GetComponent<Spawner>();
SyncHandler = new SyncHandler(Spawner.SyncDB);
}
private bool LaunchClient(string ip, int port) {
@ -100,8 +103,9 @@ namespace Cyber.Networking.Clientside {
}
ConnectionConfig Config = new ConnectionConfig();
Config.AddChannel(QosType.ReliableSequenced);
Config.AddChannel(QosType.UnreliableSequenced);
NetworkChannelID.ReliableSequenced = Config.AddChannel(QosType.ReliableSequenced);
NetworkChannelID.UnreliableSequenced = Config.AddChannel(QosType.UnreliableSequenced);
NetworkChannelID.Unreliable = Config.AddChannel(QosType.Unreliable);
NetworkServer.Configure(Config, 10);
NetClient = new NetworkClient();
@ -114,6 +118,7 @@ namespace Cyber.Networking.Clientside {
NetClient.RegisterHandler(PktType.MassIdentity, HandlePacket);
NetClient.RegisterHandler(PktType.SpawnEntity, HandlePacket);
NetClient.RegisterHandler(PktType.MoveCreature, HandlePacket);
NetClient.RegisterHandler(PktType.SyncPacket, HandlePacket);
NetClient.RegisterHandler(MsgType.Connect, OnConnected);
NetClient.RegisterHandler(MsgType.Disconnect, OnDisconnected);
@ -129,60 +134,63 @@ namespace Cyber.Networking.Clientside {
private void HandlePacket(NetworkMessage msg) {
switch (msg.msgType) {
case (PktType.TextMessage):
TextMessagePkt TextMsg = new TextMessagePkt();
TextMsg.Deserialize(msg.reader);
Term.Println(TextMsg.Message);
break;
case (PktType.Identity):
IdentityPkt Identity = new IdentityPkt();
Identity.Deserialize(msg.reader);
var Conn = new CConnectedPlayer(Identity.ConnectionID);
if (Identity.Owned) {
Player = Conn;
} else {
Debug.Log(Conn.ConnectionID + " connected!");
Term.Println(Conn.ConnectionID + " connected!");
}
Players.Add(Conn.ConnectionID, Conn);
break;
case (PktType.MassIdentity):
MassIdentityPkt Identities = new MassIdentityPkt();
Identities.Deserialize(msg.reader);
foreach (int currId in Identities.IdList) {
Players.Add(currId, new CConnectedPlayer(currId));
}
break;
case (PktType.SpawnEntity):
SpawnEntityPkt SpawnPkt = new SpawnEntityPkt();
SpawnPkt.Deserialize(msg.reader);
case (PktType.TextMessage):
TextMessagePkt TextMsg = new TextMessagePkt();
TextMsg.Deserialize(msg.reader);
Term.Println(TextMsg.Message);
break;
case (PktType.Identity):
IdentityPkt Identity = new IdentityPkt();
Identity.Deserialize(msg.reader);
var Conn = new CConnectedPlayer(Identity.ConnectionID);
if (Identity.Owned) {
Player = Conn;
} else {
Debug.Log(Conn.ConnectionID + " connected!");
Term.Println(Conn.ConnectionID + " connected!");
}
Players.Add(Conn.ConnectionID, Conn);
break;
case (PktType.MassIdentity):
MassIdentityPkt Identities = new MassIdentityPkt();
Identities.Deserialize(msg.reader);
foreach (int currId in Identities.IdList) {
Players.Add(currId, new CConnectedPlayer(currId));
}
break;
case (PktType.SpawnEntity):
SpawnEntityPkt SpawnPkt = new SpawnEntityPkt();
SpawnPkt.Deserialize(msg.reader);
EntityType EntityType = SpawnPkt.EntityType;
// Check if you are the owner and if they are spawning an NPC
if (SpawnPkt.OwnerID == Player.ConnectionID && EntityType == EntityType.NPC) {
// Change it into a PC instead.
EntityType = EntityType.PC;
}
Spawner.Spawn(EntityType, SpawnPkt.Position, SpawnPkt.SyncBaseIDList);
break;
case (PktType.MoveCreature):
MoveCreaturePkt MoveCreature = new MoveCreaturePkt();
MoveCreature.Deserialize(msg.reader);
EntityType EntityType = SpawnPkt.EntityType;
// Check if you are the owner and if they are spawning an NPC
if (SpawnPkt.OwnerID == Player.ConnectionID && EntityType == EntityType.NPC) {
// Change it into a PC instead.
EntityType = EntityType.PC;
}
Spawner.Spawn(EntityType, SpawnPkt.Position, SpawnPkt.SyncBaseIDList);
break;
case (PktType.MoveCreature):
MoveCreaturePkt MoveCreature = new MoveCreaturePkt();
MoveCreature.Deserialize(msg.reader);
SyncBase SyncBase = Spawner.SyncDB.Get(MoveCreature.SyncBaseID);
if (SyncBase != null || SyncBase is Character ) {
Character Character = (Character) SyncBase;
Character.Move(MoveCreature.Direction);
} else {
Debug.LogError("SyncBase " + MoveCreature.SyncBaseID + " is not a Creature");
Term.Println("SyncBase " + MoveCreature.SyncBaseID + " is not a Creature");
}
SyncBase SyncBase = Spawner.SyncDB.Get(MoveCreature.SyncBaseID);
if (SyncBase != null || SyncBase is Character ) {
Character Character = (Character) SyncBase;
Character.Move(MoveCreature.Direction);
} else {
Debug.LogError("SyncBase " + MoveCreature.SyncBaseID + " is not a Creature");
Term.Println("SyncBase " + MoveCreature.SyncBaseID + " is not a Creature");
}
break;
default:
Debug.LogError("Received an unknown packet, id: " + msg.msgType);
Term.Println("Received an unknown packet, id: " + msg.msgType);
break;
break;
case (PktType.SyncPacket):
SyncHandler.HandleSyncPkt(msg);
break;
default:
Debug.LogError("Received an unknown packet, id: " + msg.msgType);
Term.Println("Received an unknown packet, id: " + msg.msgType);
break;
}
}

View File

@ -0,0 +1,43 @@

using Cyber.Entities;
using Cyber.Networking.Messages;
using UnityEngine;
using UnityEngine.Networking;
namespace Cyber.Networking.Clientside {
/// <summary>
/// A Short clientside class for handling sync packages.
/// It simply keeps track of sync-packages and will not apply them if they are too old.
/// </summary>
public class SyncHandler {
private SyncDB SyncDB;
private int LatestSyncID = -1;
/// <summary>
/// Creates the SyncHandler with SyncDB.
/// </summary>
/// <param name="syncDB"></param>
public SyncHandler(SyncDB syncDB) {
SyncDB = syncDB;
}
/// <summary>
/// Handle a given Network message. Must be checked to be <see cref="PktType.SyncPacket"/> first.
/// </summary>
/// <param name="message"></param>
public void HandleSyncPkt(NetworkMessage message) {
SyncPkt SyncPacket = new SyncPkt(SyncDB);
SyncPacket.Deserialize(message.reader);
if (LatestSyncID < SyncPacket.SyncPacketID) {
LatestSyncID = SyncPacket.SyncPacketID;
SyncPacket.ApplySync(message.reader);
Debug.Log("Applied Sync " + LatestSyncID);
}
// Otherwise disregard the sync.
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6377ec5e92cc97c48aa26423059e4b3a
timeCreated: 1494337627
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,88 @@

using Cyber.Entities;
using UnityEngine.Networking;
namespace Cyber.Networking.Messages {
/// <summary>
/// Contains sync data to sync stuff with.
/// </summary>
public class SyncPkt : MessageBase {
/// <summary>
/// The Sync Packet ID of this packet.
/// </summary>
public int SyncPacketID;
/// <summary>
/// The Array of SyncID added in this SyncPkt
/// </summary>
public int[] SyncedSyncBases;
private SyncDB SyncDB;
/// <summary>
/// Creates a SyncPkt on the serverside.
/// </summary>
/// <param name="syncDB">SyncDB to sync from.</param>
/// <param name="syncBases">The ID's of the SyncBases to sync.</param>
/// <param name="syncPacketID">ID of the sync packet itself.</param>
public SyncPkt(SyncDB syncDB, int[] syncBases, int syncPacketID) {
SyncPacketID = syncPacketID;
SyncDB = syncDB;
SyncedSyncBases = syncBases;
}
/// <summary>
/// Creates SyncPkt for deserializing.
/// </summary>
/// <param name="syncDB">SyncBase to sync to.</param>
public SyncPkt(SyncDB syncDB) {
SyncDB = syncDB;
}
/// <summary>
/// Deserializes the SynkPkt with ONLY the Sync Packet ID.
/// </summary>
/// <param name="reader"></param>
public override void Deserialize(NetworkReader reader) {
SyncPacketID = reader.ReadInt32();
}
/// <summary>
/// Applies the SyncPkt.
/// </summary>
/// <param name="reader"></param>
public void ApplySync(NetworkReader reader) {
byte[][] ByteArray = new byte[4][];
ByteArray[0] = reader.ReadBytesAndSize();
ByteArray[1] = reader.ReadBytesAndSize();
ByteArray[2] = reader.ReadBytesAndSize();
ByteArray[3] = reader.ReadBytesAndSize();
SyncedSyncBases = NetworkHelper.DeserializeIntArray(ByteArray);
foreach (int syncId in SyncedSyncBases) {
SyncDB.Get(syncId).Deserialize(reader);
}
}
/// <summary>
/// Serializes the SyncPkt and writes everything it needs.
/// </summary>
/// <param name="writer"></param>
public override void Serialize(NetworkWriter writer) {
writer.Write(SyncPacketID);
byte[][] ByteArray = NetworkHelper.SerializeIntArray(SyncedSyncBases);
writer.WriteBytesFull(ByteArray[0]);
writer.WriteBytesFull(ByteArray[1]);
writer.WriteBytesFull(ByteArray[2]);
writer.WriteBytesFull(ByteArray[3]);
foreach (int syncId in SyncedSyncBases) {
SyncDB.Get(syncId).Serialize(writer);
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: b17a30eca8d025d47aa9738d5eaa9b61
timeCreated: 1494336224
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,24 @@

namespace Cyber.Networking {
/// <summary>
/// Networked channel ID's to be used.
/// </summary>
public class NetworkChannelID {
/// <summary>
/// The default channel which is reliable and sequenced. True TCP!
/// </summary>
public static byte ReliableSequenced;
/// <summary>
/// Another channel which is unreliable (like UDP) but always in correct sending order (like TCP).
/// </summary>
public static byte UnreliableSequenced;
/// <summary>
/// Very unreliable, true UDP!
/// </summary>
public static byte Unreliable;
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: daf9802139051fb4eab0348011f699af
timeCreated: 1494338663
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -32,5 +32,11 @@ namespace Cyber.Networking {
/// to inform actual movement.
/// </summary>
public const short MoveCreature = 204;
/// <summary>
/// Packet containing sync data.
/// </summary>
public const short SyncPacket = 205;
}
}

View File

@ -40,6 +40,23 @@ namespace Cyber.Networking.Serverside {
return Singleton.LaunchServer(port);
}
/// <summary>
/// Sends Message to all clients using specified channel.
/// <see cref="SendToAll(short, MessageBase)"/> defaults to <see cref="NetworkChannelID.ReliableSequenced"/>.
/// </summary>
/// <param name="msgType">Message type being sent.</param>
/// <param name="message">Message contents.</param>
/// <param name="channel">Channel being used.</param>
/// <returns></returns>
public static bool SendToAllByChannel(short msgType, MessageBase message, byte channel) {
if (NetworkServer.active) {
NetworkServer.SendByChannelToAll(msgType, message, channel);
return true;
} else {
return false;
}
}
/// <summary>
/// Attempts to send a message to all clients who are listening.
/// Returns false if server wasn't active, true otherwise.
@ -91,8 +108,9 @@ namespace Cyber.Networking.Serverside {
Spawner = GetComponent<Spawner>();
ConnectionConfig Config = new ConnectionConfig();
Config.AddChannel(QosType.ReliableSequenced);
Config.AddChannel(QosType.UnreliableSequenced);
NetworkChannelID.ReliableSequenced = Config.AddChannel(QosType.ReliableSequenced);
NetworkChannelID.UnreliableSequenced = Config.AddChannel(QosType.UnreliableSequenced);
NetworkChannelID.Unreliable = Config.AddChannel(QosType.Unreliable);
NetworkServer.Configure(Config, 10);
NetworkServer.Listen(port);
@ -112,6 +130,8 @@ namespace Cyber.Networking.Serverside {
SendToAll(PktType.TextMessage, new TextMessagePkt("Server: " + args[0]));
});
gameObject.AddComponent<Syncer>();
return true;
}
@ -129,8 +149,6 @@ namespace Cyber.Networking.Serverside {
// Check if the player is allowed to move this character
Character Controlled = Players[msg.conn.connectionId].Character.GetComponent<Character>();
Debug.Log(Controlled.ID);
Debug.Log(MoveCreature.SyncBaseID);
if (Controlled.ID != MoveCreature.SyncBaseID) {
break;
}

View File

@ -0,0 +1,95 @@

using Cyber.Entities;
using Cyber.Networking.Messages;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Cyber.Networking.Serverside {
/// <summary>
/// Keeps stuff in-sync over at clients. Periodically collects stuff that needs to be synced and then sends them on the next 'tick.'
/// </summary>
public class Syncer : MonoBehaviour {
private SyncDB Database;
private int TickCounter = 0;
private const float TickInterval = 1f / 10;
private float TimeSinceLastTick = TickInterval;
private List<int> QueuedSyncs = new List<int>();
private List<int> DirtySyncBases = new List<int>();
private int SyncPacketID = 0;
/// <summary>
/// Mark a SyncBase "Dirty", which makes it eligible to sync.
/// </summary>
/// <param name="syncBaseID">The ID of the SyncBase. See <see cref="SyncBase.ID"/></param>
public void DirtSyncBase(int syncBaseID) {
DirtySyncBases.Add(syncBaseID);
}
/// <summary>
/// Queue a SyncBase directly, so it will be synced next time a sync tick is called.
/// </summary>
/// <param name="SyncBaseID">The ID of the SyncBase. See <see cref="SyncBase.ID"/></param>
public void QueueSyncBase(int SyncBaseID) {
QueuedSyncs.Add(SyncBaseID);
}
private void Start() {
Database = GetComponent<SyncDB>();
}
private void Update() {
TimeSinceLastTick += Time.deltaTime;
if (TimeSinceLastTick >= TickInterval) {
var Categorized = Database.GetCategorizedDatabase();
foreach (Type type in Categorized.Keys) {
SyncHandletype Handletype = Database.GetSyncHandletypes()[type];
if (Handletype.RequireHash) {
foreach (int SyncBaseID in Categorized[type]) {
if (DirtySyncBases.Contains(SyncBaseID)) {
QueueSyncBase(SyncBaseID);
}
}
} else {
if (TickCounter % Handletype.TickInterval == 0) {
foreach (int SyncBaseID in Categorized[type]) {
if (DirtySyncBases.Contains(SyncBaseID)) {
QueueSyncBase(SyncBaseID);
}
}
}
}
}
TickCounter++;
TimeSinceLastTick -= TickInterval;
if (QueuedSyncs.Count > 0) {
int[] SyncIDs = QueuedSyncs.ToArray();
SyncPkt SyncPacket = new SyncPkt(Database, SyncIDs, SyncPacketID++);
Server.SendToAllByChannel(PktType.SyncPacket, SyncPacket, NetworkChannelID.Unreliable);
QueuedSyncs.Clear();
DirtySyncBases.Clear();
}
if (Categorized.ContainsKey(typeof(Character))) {
foreach (int i in Categorized[typeof(Character)]) {
DirtSyncBase(i);
}
}
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 36a5eba300b7514419b2a8df64f8e7df
timeCreated: 1494327632
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: