private void AssignSyncDataToMessage(List <SynchronisedData> dataList, EntityPropertiesMessage message, int frameTick, MxClientGroup endpoint, out bool mustBeReliable) { mustBeReliable = false; var totalString = 0; var currentString = 0; var totalInt16 = 0; var currentInt16 = 0; var totalInt32 = 0; var currentInt32 = 0; var totalSingle = 0; var currentSingle = 0; var totalDouble = 0; var currentDouble = 0; var totalBoolean = 0; var currentBoolean = 0; var totalSingleArray = 0; var currentSingleArray = 0; var totalTransform = 0; var currentTransform = 0; var typeLookup = new Dictionary <int, int>(); for (var i = 0; i < dataList.Count; i++) { if (dataList[i].CurrentValue == null) { typeLookup[i] = EntityPropertiesMessage.PropertyTypeNull; continue; } else if (dataList[i].CurrentValue is string) { typeLookup[i] = EntityPropertiesMessage.PropertyTypeString; totalString += 1; } else if (dataList[i].CurrentValue is short) { typeLookup[i] = EntityPropertiesMessage.PropertyTypeInt16; totalInt16 += 1; } else if (dataList[i].CurrentValue is int) { typeLookup[i] = EntityPropertiesMessage.PropertyTypeInt32; totalInt32 += 1; } else if (dataList[i].CurrentValue is float) { typeLookup[i] = EntityPropertiesMessage.PropertyTypeSingle; totalSingle += 1; } else if (dataList[i].CurrentValue is double) { typeLookup[i] = EntityPropertiesMessage.PropertyTypeDouble; totalDouble += 1; } else if (dataList[i].CurrentValue is bool) { typeLookup[i] = EntityPropertiesMessage.PropertyTypeBoolean; totalBoolean += 1; } else if (dataList[i].CurrentValue is Microsoft.Xna.Framework.Vector2) { typeLookup[i] = EntityPropertiesMessage.PropertyTypeVector2; totalSingleArray += 2; } else if (dataList[i].CurrentValue is Microsoft.Xna.Framework.Vector3) { typeLookup[i] = EntityPropertiesMessage.PropertyTypeVector3; totalSingleArray += 3; } else if (dataList[i].CurrentValue is Microsoft.Xna.Framework.Vector4) { typeLookup[i] = EntityPropertiesMessage.PropertyTypeVector4; totalSingleArray += 4; } else if (dataList[i].CurrentValue is Microsoft.Xna.Framework.Quaternion) { typeLookup[i] = EntityPropertiesMessage.PropertyTypeQuaternion; totalSingleArray += 4; } else if (dataList[i].CurrentValue is Microsoft.Xna.Framework.Matrix) { typeLookup[i] = EntityPropertiesMessage.PropertyTypeMatrix; totalSingleArray += 16; } else if (dataList[i].CurrentValue is Protogame.NetworkTransform) { typeLookup[i] = EntityPropertiesMessage.PropertyTypeTransform; totalTransform += 1; } else { throw new NotSupportedException("The type " + dataList[i].CurrentValue + " can not be synchronised as a network property."); } } if (totalString > 0) { message.PropertyValuesString = new string[totalString]; } if (totalInt16 > 0) { message.PropertyValuesInt16 = new short[totalInt16]; } if (totalInt32 > 0) { message.PropertyValuesInt32 = new int[totalInt32]; } if (totalSingle > 0) { message.PropertyValuesSingle = new float[totalSingle]; } if (totalDouble > 0) { message.PropertyValuesDouble = new double[totalDouble]; } if (totalBoolean > 0) { message.PropertyValuesBoolean = new bool[totalBoolean]; } if (totalSingleArray > 0) { message.PropertyValuesSingleArray = new float[totalSingleArray]; } if (totalTransform > 0) { message.PropertyValuesTransform = new Protogame.NetworkTransform[totalTransform]; } for (var ix = 0; ix < dataList.Count; ix++) { message.PropertyNames[ix] = dataList[ix].Name; message.PropertyTypes[ix] = typeLookup[ix]; // Update synchronisation data. dataList[ix].LastFrameSynced[endpoint] = frameTick; if (!dataList[ix].HasPerformedInitialSync.GetOrDefault(endpoint)) { dataList[ix].HasPerformedInitialSync[endpoint] = true; mustBeReliable = true; } object currentValue = dataList[ix].CurrentValue; switch (typeLookup[ix]) { case EntityPropertiesMessage.PropertyTypeNull: // Do nothing. break; case EntityPropertiesMessage.PropertyTypeString: { string value = (string)currentValue; message.PropertyValuesString[currentString++] = value; } break; case EntityPropertiesMessage.PropertyTypeInt16: { short value = (short)currentValue; message.PropertyValuesInt16[currentInt16++] = value; } break; case EntityPropertiesMessage.PropertyTypeInt32: { int value = (int)currentValue; message.PropertyValuesInt32[currentInt32++] = value; } break; case EntityPropertiesMessage.PropertyTypeSingle: { float value = (float)currentValue; message.PropertyValuesSingle[currentSingle++] = value; } break; case EntityPropertiesMessage.PropertyTypeDouble: { double value = (double)currentValue; message.PropertyValuesDouble[currentDouble++] = value; } break; case EntityPropertiesMessage.PropertyTypeBoolean: { bool value = (bool)currentValue; message.PropertyValuesBoolean[currentBoolean++] = value; } break; case EntityPropertiesMessage.PropertyTypeVector2: { var value = ConvertToVector2(currentValue); message.PropertyValuesSingleArray[currentSingleArray++] = value[0]; message.PropertyValuesSingleArray[currentSingleArray++] = value[1]; } break; case EntityPropertiesMessage.PropertyTypeVector3: { var value = ConvertToVector3(currentValue); message.PropertyValuesSingleArray[currentSingleArray++] = value[0]; message.PropertyValuesSingleArray[currentSingleArray++] = value[1]; message.PropertyValuesSingleArray[currentSingleArray++] = value[2]; } break; case EntityPropertiesMessage.PropertyTypeVector4: { var value = ConvertToVector4(currentValue); message.PropertyValuesSingleArray[currentSingleArray++] = value[0]; message.PropertyValuesSingleArray[currentSingleArray++] = value[1]; message.PropertyValuesSingleArray[currentSingleArray++] = value[2]; message.PropertyValuesSingleArray[currentSingleArray++] = value[3]; } break; case EntityPropertiesMessage.PropertyTypeQuaternion: { var value = ConvertToQuaternion(currentValue); message.PropertyValuesSingleArray[currentSingleArray++] = value[0]; message.PropertyValuesSingleArray[currentSingleArray++] = value[1]; message.PropertyValuesSingleArray[currentSingleArray++] = value[2]; message.PropertyValuesSingleArray[currentSingleArray++] = value[3]; } break; case EntityPropertiesMessage.PropertyTypeMatrix: { var value = ConvertToMatrix(currentValue); message.PropertyValuesSingleArray[currentSingleArray++] = value[0]; message.PropertyValuesSingleArray[currentSingleArray++] = value[1]; message.PropertyValuesSingleArray[currentSingleArray++] = value[2]; message.PropertyValuesSingleArray[currentSingleArray++] = value[3]; message.PropertyValuesSingleArray[currentSingleArray++] = value[4]; message.PropertyValuesSingleArray[currentSingleArray++] = value[5]; message.PropertyValuesSingleArray[currentSingleArray++] = value[6]; message.PropertyValuesSingleArray[currentSingleArray++] = value[7]; message.PropertyValuesSingleArray[currentSingleArray++] = value[8]; message.PropertyValuesSingleArray[currentSingleArray++] = value[9]; message.PropertyValuesSingleArray[currentSingleArray++] = value[10]; message.PropertyValuesSingleArray[currentSingleArray++] = value[11]; message.PropertyValuesSingleArray[currentSingleArray++] = value[12]; message.PropertyValuesSingleArray[currentSingleArray++] = value[13]; message.PropertyValuesSingleArray[currentSingleArray++] = value[14]; message.PropertyValuesSingleArray[currentSingleArray++] = value[15]; } break; case EntityPropertiesMessage.PropertyTypeTransform: { Protogame.NetworkTransform value = (Protogame.NetworkTransform)currentValue; message.PropertyValuesTransform[currentTransform++] = value; } break; } } }
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); } } } } } }
private void AssignMessageToSyncData(EntityPropertiesMessage message, Dictionary <string, SynchronisedData> fullDataList, MxClientGroup endpoint) { var currentString = 0; var currentInt16 = 0; var currentInt32 = 0; var currentSingle = 0; var currentDouble = 0; var currentBoolean = 0; var currentSingleArray = 0; var currentTransform = 0; for (var i = 0; i < message.PropertyNames.Length; i++) { if (!fullDataList.ContainsKey(message.PropertyNames[i])) { continue; } var syncData = fullDataList[message.PropertyNames[i]]; var hasValue = false; object value = null; if (message.MessageOrder <= syncData.LastMessageOrder) { // This property is already at a later version. continue; } switch (message.PropertyTypes[i]) { case EntityPropertiesMessage.PropertyTypeNone: break; case EntityPropertiesMessage.PropertyTypeNull: value = null; hasValue = true; syncData.HasReceivedInitialSync[endpoint] = true; break; case EntityPropertiesMessage.PropertyTypeString: { value = message.PropertyValuesString[currentString]; hasValue = true; syncData.HasReceivedInitialSync[endpoint] = true; currentString++; break; } case EntityPropertiesMessage.PropertyTypeInt16: { value = message.PropertyValuesInt16[currentInt16]; hasValue = true; syncData.HasReceivedInitialSync[endpoint] = true; currentInt16++; break; } case EntityPropertiesMessage.PropertyTypeInt32: { value = message.PropertyValuesInt32[currentInt32]; hasValue = true; syncData.HasReceivedInitialSync[endpoint] = true; currentInt32++; break; } case EntityPropertiesMessage.PropertyTypeSingle: { value = message.PropertyValuesSingle[currentSingle]; hasValue = true; syncData.HasReceivedInitialSync[endpoint] = true; currentSingle++; break; } case EntityPropertiesMessage.PropertyTypeDouble: { value = message.PropertyValuesDouble[currentDouble]; hasValue = true; syncData.HasReceivedInitialSync[endpoint] = true; currentDouble++; break; } case EntityPropertiesMessage.PropertyTypeBoolean: { value = message.PropertyValuesBoolean[currentBoolean]; hasValue = true; syncData.HasReceivedInitialSync[endpoint] = true; currentBoolean++; break; } case EntityPropertiesMessage.PropertyTypeVector2: { value = ConvertFromVector2(message.PropertyValuesSingleArray, currentSingleArray); hasValue = true; syncData.HasReceivedInitialSync[endpoint] = true; currentSingleArray += 2; break; } case EntityPropertiesMessage.PropertyTypeVector3: { value = ConvertFromVector3(message.PropertyValuesSingleArray, currentSingleArray); hasValue = true; syncData.HasReceivedInitialSync[endpoint] = true; currentSingleArray += 3; break; } case EntityPropertiesMessage.PropertyTypeVector4: { value = ConvertFromVector4(message.PropertyValuesSingleArray, currentSingleArray); hasValue = true; syncData.HasReceivedInitialSync[endpoint] = true; currentSingleArray += 4; break; } case EntityPropertiesMessage.PropertyTypeQuaternion: { value = ConvertFromQuaternion(message.PropertyValuesSingleArray, currentSingleArray); hasValue = true; syncData.HasReceivedInitialSync[endpoint] = true; currentSingleArray += 4; break; } case EntityPropertiesMessage.PropertyTypeMatrix: { value = ConvertFromMatrix(message.PropertyValuesSingleArray, currentSingleArray); hasValue = true; syncData.HasReceivedInitialSync[endpoint] = true; currentSingleArray += 16; break; } case EntityPropertiesMessage.PropertyTypeTransform: { value = message.PropertyValuesTransform[currentTransform]; hasValue = true; syncData.HasReceivedInitialSync[endpoint] = true; currentTransform++; break; } } if (hasValue) { syncData.LastValueFromServer = value; syncData.LastMessageOrder = message.MessageOrder; if (syncData.TimeMachine == null) { syncData.SetValueDelegate(value); } else { syncData.TimeMachine.Set(message.FrameTick, value); } } } }