private void Init() { if (doneInit) { return; } parameters = Animator.parameters; int count = parameters.Length; if (parameters.Length > 256) { count = 256; JNet.Error($"There are {parameters.Length} animator parameters on object {gameObject.name}, but only 256 can be networked. Some will not be syncronized. Why do you have that many anyway..."); } paramDict = new Dictionary <string, byte>(); for (int i = 0; i < count; i++) { var param = parameters[i]; paramDict.Add(param.name.Trim(), (byte)i); } doneInit = true; }
private void LateUpdate() { if (!updateParent) { return; } updateParent = false; if (ParentNetID != 0 && ParentNodeID != 0) { // Try to find the object. NetObject obj = JNet.GetObject(ParentNetID); if (obj == null) { JNet.Error($"Failed to find net object of id {ParentNetID} to sync the transform of this object."); return; } var node = obj.GetParentNode(ParentNodeID); if (node == null) { JNet.Error($"Failed to find net object of id {ParentNetID} parent node ({ParentNodeID}) to sync the transform of this object."); return; } this.transform.SetParent(node.GetTransform(), true); } else { this.transform.SetParent(null, true); } }
private object[] MsgToArgs(MethodInfo f, NetIncomingMessage msg) { MethodArgs.Clear(); var parameters = f.GetParameters(); if (Parsers.Count == 0) { AddAllParsers(); } for (int i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; Type type = parameter.ParameterType; if (!Parsers.ContainsKey(type)) { JNet.Error($"Failed to find parser for incoming data type: {type.FullName}. Method invocation will probably fail."); continue; } ReadWrite rw = Parsers[type]; object arg = rw(true, msg, null, null); MethodArgs.Add(arg); } return(MethodArgs.ToArray()); }
private bool ArgsToMsg(MethodInfo f, object[] args, NetOutgoingMessage msg) { var parameters = f.GetParameters(); if (parameters.Length != args.Length) { JNet.Error($"Expected {parameters.Length} parameters for remote invocation of {f.Name}, but only {args.Length} were supplied. Method will not be called."); return(false); } if (Parsers.Count == 0) { AddAllParsers(); } for (int i = 0; i < parameters.Length; i++) { var param = parameters[i]; Type type = param.ParameterType; Type realType = args[i].GetType(); if (!type.IsAssignableFrom(realType)) { JNet.Error($"Cannot automatically cast argument of type {realType.Name} to param {param.Name} of type {type.Name}."); return(false); } ReadWrite rw = Parsers[type]; rw(false, null, msg, args[i]); } return(true); }
public bool Update() { if (JNet.IsServer) { return(NetDirty); } if (objID == 0) { if (obj != null) { obj = null; UponObjectUpdate?.Invoke(null); } } else { if (obj == null || obj.NetID != objID) { var found = JNet.GetObject(objID); if (found != null) { obj = found; UponObjectUpdate?.Invoke(obj); } } } return(false); }
private void ProcessDespawn(NetIncomingMessage msg) { // Do not process if the server is active. if (JNet.IsServer) { return; } // Net ID. ushort netID = msg.ReadUInt16(); if (netID == 0) { JNet.Error("Client was instructed to despawn object with netID 0, server error or corruption?"); return; } NetObject obj = JNet.Tracker.TrackedObjects[netID]; if (obj == null) { // Hmmm... JNet.Error(string.Format("Client was instructed to depsnaw object of NetID {0}, but that object was not found. Already destroyed?", netID)); return; } // Destroy the object and unregister it. JNet.Tracker.Unregister(obj); // Destroy. GameObject.Destroy(obj.gameObject); }
protected override void ProcessStatusChanged(NetIncomingMessage msg, NetConnectionStatus status) { base.ProcessStatusChanged(msg, status); if (status == NetConnectionStatus.Connected) { Log("New client connected from {0}", msg.SenderConnection.RemoteEndPoint); // Send currently tracked objects, if not host. if (msg.SenderConnection != LocalClientConnection) { SendAllObjects(msg.SenderConnection); } RemoteClient c = GetClient(msg); UponConnection?.Invoke(c); } if (status == NetConnectionStatus.Disconnected) { string reason = msg.ReadString(); Log("Client has disconnected: {0}", reason); if (msg.SenderConnection == LocalClientConnection) { Log("(Was local client)"); LocalClientConnection = null; } var client = GetClient(msg); if (client != null) { // Remove net object from this client's ownership. int loop = 0; int max = client.OwnedObjectsList.Count; while (client.OwnedObjectsList.Count > 0) { JNet.SetOwner(client.OwnedObjectsList[0], null); loop++; if (loop > max) { JNet.Error($"Infinite loop?! Looped {loop}, expected {max}."); break; } } // Remove client. this.Clients.Remove(client); this.dictClients.Remove(client.ConnectionID); UponDisconnection?.Invoke(client, reason); } else { LogError("Client with Id {0} from {1} did not have a valid RemoteClient to remove.", msg.SenderConnection.RemoteUniqueIdentifier, msg.SenderConnection.RemoteEndPoint); } } }
private void CheckDeliveryMethod() { if (RMCDeliveryMethod == NetDeliveryMethod.ReliableSequenced || RMCDeliveryMethod == NetDeliveryMethod.UnreliableSequenced) { NetDeliveryMethod replacement = RMCDeliveryMethod == NetDeliveryMethod.ReliableSequenced ? NetDeliveryMethod.ReliableOrdered : NetDeliveryMethod.Unreliable; JNet.Error($"Remote Delivery Method {RMCDeliveryMethod} is not allowed because it is sequenced. Using {replacement} instead."); RMCDeliveryMethod = replacement; } }
/// <summary> /// Invokes the named method on the corresponding behaviour on the server. /// When called on the server, the target method will be instantly invoked. /// </summary> /// <param name="methodName">The name of the method to call. The method should have the [Cmd] attribute.</param> /// <param name="args">The arguments to pass to the method.</param> protected void InvokeCMD(string methodName, params object[] args) { if (!NetObject.HasNetID) { JNet.Error($"Cannot call Cmd {methodName} because the object that {GetType().FullName} is on is not net spawned."); return; } MethodInfo method = GetCmd(methodName); if (method == null) { JNet.Error($"Could not find valid method with Cmd tag {methodName}. Check tags, name spelling and regenerate netcode!"); return; } if (!IsServer && !IsClient) { JNet.Error($"Not on client or server, can't invoke cmd {methodName}"); return; } if (!HasAuthority) { JNet.Error($"There is currently no authority over this object {name}. Must call from on a client that has object ownership, or on the server. Current owner: {NetObject.OwnerID}"); return; } methodName = methodName.Trim().ToLower(); byte methodID = CustomGeneratedBehaviour.GetMethodID(methodName); if (!JNet.IsServer) { CheckDeliveryMethod(); NetOutgoingMessage msg = JNet.GetClient().CreateMessage(Internal.JDataType.RMC); msg.Write(NetObject.NetID); msg.Write(this.BehaviourID); msg.Write(methodID); bool worked = ArgsToMsg(method, args, msg); if (!worked) { JNet.Error("Did not invoke Cmd due to error."); return; } var client = JNet.GetClient(); client.Send(msg, RMCDeliveryMethod, 24); } else { // Invoke directly. var m = CustomGeneratedBehaviour.GetCmd(methodName); m.Invoke(this, args); } }
private static void AddAllParsers() { AddParser( (m) => m.ReadBoolean(), (m, o) => m.Write((bool)o)); AddParser( (m) => m.ReadColor(), (m, o) => m.Write((Color)o)); AddParser( (m) => m.ReadString(), (m, o) => m.Write((string)o)); AddParser( (m) => m.ReadByte(), (m, o) => m.Write((byte)o)); AddParser( (m) => m.ReadSByte(), (m, o) => m.Write((sbyte)o)); AddParser( (m) => m.ReadInt16(), (m, o) => m.Write((short)o)); AddParser( (m) => m.ReadUInt16(), (m, o) => m.Write((ushort)o)); AddParser( (m) => m.ReadInt32(), (m, o) => m.Write((int)o)); AddParser( (m) => m.ReadUInt32(), (m, o) => m.Write((uint)o)); AddParser( (m) => m.ReadInt64(), (m, o) => m.Write((long)o)); AddParser( (m) => m.ReadUInt64(), (m, o) => m.Write((ulong)o)); AddParser( (m) => m.ReadFloat(), (m, o) => m.Write((float)o)); AddParser( (m) => m.ReadDouble(), (m, o) => m.Write((double)o)); AddParser( (m) => m.ReadDecimal(), (m, o) => m.Write((decimal)o)); AddParser( (m) => m.ReadVector2(), (m, o) => m.Write((Vector2)o)); AddParser( (m) => m.ReadVector3(), (m, o) => m.Write((Vector3)o)); AddParser( (m) => m.ReadVector4(), (m, o) => m.Write((Vector4)o)); AddParser( (m) => JNet.GetObject(m.ReadUInt16()), (m, o) => m.Write((ushort)o)); }
private void ProcessSetOwner(NetIncomingMessage msg) { ushort netID = msg.ReadUInt16(); long ownerID = msg.ReadInt64(); var obj = JNet.GetObject(netID); if (obj != null) { obj.OwnerID = ownerID; } }
/// <summary> /// Connects the active client [see <see cref="StartClient"/>] to the specified address and port. /// If attempting to connect the client to the local device, ALWAYS USE <see cref="ConnectClientToHost(NetOutgoingMessage)"/>. /// Only works if the client is not already connected or connecting. /// </summary> /// <param name="host">The address to connect to. Use 127.0.0.1 to connect to a local server.</param> /// <param name="port">The port number to connect on.</param> /// <param name="msg">A hail message. The contents of this message will be read in <see cref="JNetServer.UponConnectionRequest"/> if this behaviour is enabled.</param> public static void ConnectClientToRemote(string host, int port, NetOutgoingMessage msg) { if (!Initialized) { Error("Call Init() before connecting the client."); return; } if (!IsClient) { Error("Client is not created. Call StartClient before trying to connect."); return; } if (ClientConnectonStatus != NetConnectionStatus.Disconnected) { Error("Client is already connected, connecting or disconnecting. Cannot start connect now."); return; } host = host.Trim(); if (host == "localhost" || host == "127.0.0.1" && IsServer) { Error("Possibly attempting to connect to local server using ConnectToRemote. Consider ConnectClientToHost instead."); // return; } if (!IsServer) { Tracker.Reset(); if (EnableSceneObjectNetworking) { RegisterAllSceneObjects(); } } NetOutgoingMessage required = Client.CreateMessage(); required.Write(false); required.Write(double.MinValue); if (msg != null) { Client.Connect(host, port, JNet.CreateCombinedMessage(required, msg, false)); } else { Client.Connect(host, port, required); } Log("Started client connect to " + host + " on port " + port); }
/// <summary> /// Invokes the named method on the behaviours of all connected clients. /// Therefore, this is only valid when called on the server. /// If the server is a host (client & server at the same time), then the method is also invoked /// locally. /// </summary> /// <param name="methodName">The name of the method to invoke.</param> /// <param name="args"></param> protected void InvokeRPC(string methodName, params object[] args) { if (!JNet.IsServer) { JNet.Error($"Cannot call Rpc {methodName} because server is not active!"); return; } if (!NetObject.HasNetID) { JNet.Error($"Cannot call Rpc {methodName} because the object that {GetType().FullName} is on is not net spawned."); return; } MethodInfo method = GetRpc(methodName); if (method == null) { JNet.Error($"Could not find valid method with Rpc tag {methodName}. Check tags, name spelling and regenerate netcode!"); return; } methodName = methodName.Trim().ToLower(); byte methodID = CustomGeneratedBehaviour.GetMethodID(methodName); CheckDeliveryMethod(); NetOutgoingMessage msg = JNet.GetServer().CreateMessage(Internal.JDataType.RMC); msg.Write(NetObject.NetID); msg.Write(this.BehaviourID); msg.Write(methodID); bool worked = ArgsToMsg(method, args, msg); if (!worked) { JNet.Error("Did not invoke Rpc due to error."); return; } var server = JNet.GetServer(); server.SendToAllExcept(server.LocalClientConnection, msg, RMCDeliveryMethod, 24); // Call on local client, if we are a local client... if (JNet.IsClient) { method.Invoke(this, args); } }
private void ProcessSpawnAll(NetIncomingMessage msg) { int objectCount = msg.ReadInt32(); int expectedSceneObjects = JNet.EnableSceneObjectNetworking ? JNet.TrackedObjectCount : 0; JNet.Log($"Recieved {objectCount} objects from the server."); for (int i = 0; i < objectCount; i++) { ushort prefabID = msg.ReadUInt16(); ushort netID = msg.ReadUInt16(); // Get the prefab for that ID... NetObject prefab = JNet.GetPrefab(prefabID); if (prefab == null && netID > expectedSceneObjects) { JNet.Error("Failed to find prefab for ID " + prefabID + ", object not spawned on client. Desync incoming!"); return; } if (netID == 0) { JNet.Error("Client got spawn message with instance net ID of zero, server error or corruption? Object not spawned, prefab id was " + prefabID + " (" + prefab + ")."); return; } // Instantiate the game object, if not scene object. NetObject no; if (prefab != null) { no = GameObject.Instantiate(prefab); } else { no = JNet.GetObject(netID); } // We need to register the object to the system, if not scene object. if (prefab != null) { JNet.Tracker.Register(no, netID); } // Read all the behaviours. no.Deserialize(msg, true); } UponWorldRecieved?.Invoke(); }
public void Deserialize(NetIncomingMessage msg, bool first) { ushort id = msg.ReadUInt16(); if (objID != id) { this.objID = id; var found = JNet.GetObject(id); if (found != obj || first) { obj = found; UponObjectUpdate?.Invoke(obj); } } }
public JNetServer(string name, NetPeerConfiguration config) : base(config, true) { if (string.IsNullOrWhiteSpace(name)) { JNet.Error(string.Format("Null name supplied to server, default name '{0}' will be used.", DEFAULT_NAME)); name = DEFAULT_NAME; } Name = name.Trim(); Clients = new List <RemoteClient>(); dictClients = new Dictionary <long, RemoteClient>(); SetProcessor(JDataType.PING, this.ProcessPing); SetProcessor(JDataType.RMC, this.ProcessRMC); }
public void Set(NetObject value) { if (!JNet.IsServer) { JNet.Error("Cannot set value of net reference when not on server."); return; } if (obj == value) { return; } obj = value; objID = value?.NetID ?? 0; NetDirty = true; }
/// <summary> /// Connects the active client [see <see cref="StartClient"/>] to the locally running server. /// It is important to use this method when connecting to the local server since it allows the server /// to exclude it from certain messages. /// Only works if the client is not already connected or connecting. /// </summary> /// <param name="msg">The optional hail/connection request message. Can be null.</param> public static void ConnectClientToHost(NetOutgoingMessage msg) { if (!Initialized) { Error("Call Init() before connecting the client."); return; } if (!IsClient) { Error("Client is not created. Call StartClient before trying to connect."); return; } if (ClientConnectonStatus != NetConnectionStatus.Disconnected) { Error("Client is already connected, connecting or disconnecting. Cannot start connect now."); return; } if (!IsServer) { Error("Server is not running on this instance, cannot connect client as host. Use JNet.ConnectClientToRemote instead."); return; } Client.IsHost = true; NetOutgoingMessage required = Client.CreateMessage(); required.Write(true); required.Write(Server.GenNewHostKey()); if (msg != null) { Client.Connect("127.0.0.1", Server.Port, JNet.CreateCombinedMessage(required, msg, false)); } else { Client.Connect("127.0.0.1", Server.Port, required); } Log("Started client connect to (localhost) on port " + Server.Port); }
internal void NetAwake() { // This (derived) class should have a corresponding autogenerated netcode class. Let's find it! Type c = GetGeneratedType(this.GetType()); if (c == null) { JNet.Error($"Failed to find network autogenerated class for {this.GetType().FullName}, perhaps network code needs to be regenerated?"); #if UNITY_EDITOR UnityEditor.EditorApplication.isPaused = true; #endif return; } var constructor = c.GetConstructors()[0]; var instance = constructor.Invoke(null); CustomGeneratedBehaviour = instance as JNetGeneratedBehaviour; }
internal void HandleRMCMessage(NetIncomingMessage msg, byte methodID) { // TODO catch exceptions. bool expectCmd = JNet.IsServer; string methodName = CustomGeneratedBehaviour.GetMethodName(methodID); (var method, bool isCmd) = CustomGeneratedBehaviour.RemoteMethods[methodName]; if (expectCmd != isCmd) { JNet.Error("Wrong remote type?? Invesitgate."); return; } if (isCmd) { // Check message sender for ownership of this object. long owner = this.NetObject.OwnerID; long messageSender = msg.SenderConnection.RemoteUniqueIdentifier; if (owner != messageSender) { JNet.Error($"Got CMD from a client for an object that they don't own. Serious bug or hacking attempt? owner ID: {owner}, message sender: {messageSender}."); return; } } // Read arguments. object[] args = MsgToArgs(method, msg); try { method.Invoke(this, args); } catch (Exception e) { JNet.Error($"Exception invoking {(isCmd ? "CMD" : "RPC")} {method.Name} - {e.GetType().Name}: {e.Message}"); } }
public void SerializeAll() { if (!JNet.IsServer) { JNet.Error("Not on server, cannot serialize all."); return; } for (int i = 0; i < ActiveObjects.Count; i++) { var obj = ActiveObjects[i]; if(obj == null) { JNet.Error("Null object in tracked objects list."); ActiveObjects.RemoveAt(i); i--; continue; } if(obj.NetBehaviours != null) { foreach (var b in obj.NetBehaviours) { if(b != null && b.NetDirty) { JNetServer server = JNet.GetServer(); NetOutgoingMessage msg = server.CreateMessage(); msg.Write((byte)JDataType.SERIALIZED); msg.Write(obj.NetID); msg.Write(b.BehaviourID); b.NetSerialize(msg, false); server.SendToAllExcept(server.LocalClientConnection, msg, b.SerializationDeliveryMethod, 1); b.NetDirty = false; } } } } }
private void ProcessSpawn(NetIncomingMessage msg) { // Do not process if the server is active. if (JNet.IsServer) { return; } // Prefab ID. ushort prefabID = msg.ReadUInt16(); // Net ID. ushort netID = msg.ReadUInt16(); // Get the prefab for that ID... NetObject prefab = JNet.GetPrefab(prefabID); if (prefab == null) { JNet.Error("Failed to find prefab for ID " + prefabID + ", object not spawned on client. Desync incoming!"); return; } if (netID == 0) { JNet.Error("Client got spawn message with instance net ID of zero, server error or corruption? Object not spawned, prefab id was " + prefabID + " (" + prefab + ")."); return; } // Instantiate the game object. NetObject no = GameObject.Instantiate(prefab); // We need to register the object to the system. JNet.Tracker.Register(no, netID); // Read all the behaviours. no.Deserialize(msg, true); }
internal void UpdateBehaviourList() { var comps = this.GetComponents <NetBehaviour>(); if (comps.Length <= 256) { NetBehaviours = comps; } else { NetBehaviours = new NetBehaviour[256]; System.Array.Copy(comps, NetBehaviours, 256); JNet.Error(string.Format("NetObject {0} has {1} NetBehaviour components, but a max of 256 are supported. Some of them will not function properly, and errors will arise.", this.ToString(), comps.Length)); } for (int i = 0; i < NetBehaviours.Length; i++) { NetBehaviours[i].BehaviourID = (byte)i; NetBehaviours[i].NetAwake(); } RefreshParentNodes(); }
public void Unregister(NetObject obj) { if (obj == null) return; if (!obj.HasNetID) return; ushort id = obj.NetID; NetObject current = TrackedObjects[id]; if(current != obj) { JNet.Error("Current is not the object that is about to be unregistered."); return; } TrackedObjects[id] = null; obj.NetID = 0; ObjectCount--; ActiveObjects.Remove(obj); }
internal static void UpdateClassMap() { classMap.Clear(); var s = new System.Diagnostics.Stopwatch(); s.Start(); Type parent = typeof(JNetGeneratedBehaviour); uint typeCount = 0; uint assemblyCount = 0; var assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (var assembly in assemblies) { string name = assembly.GetName().Name; if (name.StartsWith("Unity")) { continue; } if (name.StartsWith("Mono")) { continue; } if (name.StartsWith("System")) { continue; } if (name.StartsWith("Microsoft")) { continue; } if (name == "mscorlib") { continue; } // JNet.Log(name); assemblyCount++; var types = assembly.GetTypes(); foreach (var type in types) { typeCount++; if (!type.IsClass) { continue; } if (!type.IsSealed) { continue; } if (type.IsSubclassOf(parent)) { var attribute = type.GetCustomAttribute <GeneratedTargetAttribute>(); if (attribute != null) { var targetType = attribute.GetTargetType(); classMap.Add(targetType, type); } } } } s.Stop(); JNet.Log(string.Format("Rebuild class map in {0}ms, scanned {1} assemblies and {2} types.", s.Elapsed.TotalMilliseconds, assemblyCount, typeCount)); }
/// <summary> /// Makes a spawned object be registered to the networking system, causing it to be /// instantiated on all connected clients. /// Note that this does NOT instantiate the object on the server. The object should already be instantiated, /// and this method just registers it. In other words, do not pass a prefab to this method. /// </summary> /// <param name="obj">A behaviour on the net object to spawn.</param> public static void Spawn(NetBehaviour behaviour, NetConnection owner = null) { JNet.Spawn(behaviour.NetObject, owner); }
private void OnDestroy() { JNet.Despawn(this); }
public void Register(NetObject obj, ushort overrideNetID = 0) { if (obj == null) { JNet.Error("Null object to register."); return; } if (obj.HasNetID) { JNet.Error("Object already registered!"); return; } ushort id = 0; if(overrideNetID == 0) { int maxID = TrackedObjects.Length; for (int i = 0; i < maxID + 1; i++) { id = CurrentTopID; CurrentTopID++; if (CurrentTopID >= maxID) { CurrentTopID = 1; } if (TrackedObjects[id] == null) { break; } else { id = 0; } } } else { id = overrideNetID; } if(id == 0) { JNet.Error("Cannot register new net object to be tracked, out of ID's! Too many objects are already tracked! Max object count: " + MAX_TRACKED_OBJECTS); return; } obj.NetID = id; obj.UpdateBehaviourList(); if(TrackedObjects[id] != null) { JNet.Error("Overriding object in register, possibly from client. Override netID: " + overrideNetID); } TrackedObjects[id] = obj; ObjectCount++; ActiveObjects.Add(obj); }
public static NetObject ReadNetObject(this NetIncomingMessage msg) { return(JNet.GetObject(msg.ReadUInt16())); }
protected void LogError(string s, params object[] args) { JNet.Error((IsServer ? "[S] " : "[C] ") + string.Format(s, args)); }