private void PrepareAndTransmitSynchronisation(ComponentizedEntity entity, int tick, bool isFromClient, ClientAuthoritiveMode clientAuthoritiveMode) { if (!_uniqueIdentifierForEntity.HasValue) { throw new InvalidOperationException("PrepareAndTransmit should not be called without an entity ID!"); } // If the entity is a synchronised entity, collect properties of the synchronised object // directly. var synchronisedEntity = entity as ISynchronisedObject; if (synchronisedEntity != null) { _synchronisationContext = synchronisedEntity; _synchronisationContext.DeclareSynchronisedProperties(this); } // Iterate through all the components on the entity and get their synchronisation data as well. foreach (var synchronisedComponent in entity.Components.OfType <ISynchronisedObject>()) { _synchronisationContext = synchronisedComponent; _synchronisationContext.DeclareSynchronisedProperties(this); } // Sync properties to each client. foreach (var dispatcher in _networkEngine.CurrentDispatchers) { foreach (var group in dispatcher.ValidClientGroups) { if (ClientAuthoritiveMode != ClientAuthoritiveMode.None && ClientOwnership != null && OnlySendToAuthoritiveClient) { if (ClientOwnership != group) { // This client doesn't own the entity, and this entity is only // synchronised with clients that own it. continue; } } if (isFromClient || _clientsEntityIsKnownOn.Contains(group)) { if (_synchronisedData.Count > 0) { // Now calculate the delta to transmit over the network. var currentTick = tick; // TODO: Use TimeTick _synchronisedDataToTransmit.Clear(); foreach (var data in _synchronisedData.Values) { var needsSync = false; // Check the target for this data to see whether or not we send it to // this particular client. if (data.SynchronisationTargets == SynchroniseTargets.OwningClient && (ClientOwnership == null || !(ClientOwnership == group))) { // This data should only be synchronised to the owning client, and // we are not the owning client. continue; } else if (data.SynchronisationTargets == SynchroniseTargets.NonOwningClients && (ClientOwnership == null || ClientOwnership == group)) { // This data should only be synchronised to non-owning clients, and // we either are the owning client, or no client ownership has been set. continue; } // If we're on the client and we haven't had an initial piece of data from the server, // we never synchronise because we don't know what the initial value is. if (isFromClient && !data.HasReceivedInitialSync.GetOrDefault(group, false)) { continue; } // If we haven't performed the initial synchronisation, we always transmit the data. if (!data.HasPerformedInitialSync.GetOrDefault(group, false)) { _networkEngine.LogSynchronisationEvent( "Must send property '" + data.Name + "' on entity ID " + _uniqueIdentifierForEntity + " because the endpoint " + group + " has not received it's initial sync."); needsSync = true; } // If we are on the client (i.e. the client assumes it's authoritive), or if the // server knows that the client does not have authority, then allow this next section. // Or to put it another way, if we're not on the client and we know the client has // authority, only transmit data for the first time because the client will make // decisions from that point onwards. if (isFromClient || clientAuthoritiveMode != ClientAuthoritiveMode.TrustClient || (clientAuthoritiveMode == ClientAuthoritiveMode.TrustClient && group != ClientOwnership)) { var lastValue = data.LastValue; var currentValue = data.CurrentValue; if (lastValue is ITransform) { throw new InvalidOperationException( "Last value property got stored as ITransform, but should have been stored as NetworkTransform!"); } if (currentValue is ITransform) { currentValue = ((ITransform)currentValue).SerializeToNetwork(); } if (!Equals(lastValue, currentValue)) { if (data.LastFrameSynced.GetOrDefault(group, 0) + data.FrameInterval < currentTick) { _networkEngine.LogSynchronisationEvent( "Sending property '" + data.Name + "' on entity ID " + _uniqueIdentifierForEntity + " because the value has changed (old value: " + lastValue + ", new value: " + currentValue + ")," + " and the next frame synced target for group " + group + "" + " is " + (data.LastFrameSynced.GetOrDefault(group, 0) + data.FrameInterval) + "" + " and the current tick is " + currentTick + "."); needsSync = true; } } } if (needsSync) { _synchronisedDataToTransmit.Add(data); } } if (_synchronisedDataToTransmit.Count > 0) { // Build up the synchronisation message. var message = new EntityPropertiesMessage(); message.EntityID = _uniqueIdentifierForEntity.Value; message.FrameTick = currentTick; message.PropertyNames = new string[_synchronisedDataToTransmit.Count]; message.PropertyTypes = new int[_synchronisedDataToTransmit.Count]; message.IsClientMessage = isFromClient; message.MessageOrder = _messageOrder++; bool reliable; AssignSyncDataToMessage(_synchronisedDataToTransmit, message, currentTick, group, out reliable); // Send an entity properties message to the client. _networkEngine.Send( dispatcher, group, message, reliable); } } } } } }