public void ReleaseControlInternal(IMessageRider token) { NetAssert.True(Flags & EntityFlags.HAS_CONTROL); Flags &= ~EntityFlags.HAS_CONTROL; CommandQueue.Clear(); CommandSequence = 0; CommandLastExecuted = null; ControlLostToken = token; ControlGainedToken = null; // call to serializer Serializer.OnControlLost(); // call to user behaviours foreach (IEntityBehaviour eb in Behaviours) { if (ReferenceEquals(eb.entity, this.UnityObject)) { eb.ControlLost(); } } // call user event GlobalEventListenerBase.ControlOfEntityLostInvoke(UnityObject); // de-freeze Freeze(false); }
public BasePacket Acquire() { BasePacket stream = null; lock (pool) { if (pool.Count > 0) { stream = pool.Pop(); } } if (stream == null) { stream = new BasePacket(new byte[1500]); stream.pool = this; } NetAssert.True(stream.isPooled); stream.isPooled = false; stream.Position = 0; stream.Size = (1500) << 3; return(stream); }
public static float InterpolateFloat(ListExtended <NetworkStorage> frames, int offset, int frame, bool angle) { var f0 = frames.First; var p0 = f0.Values[offset].Float1; if ((frames.Count == 1) || (f0.Frame >= frame)) { return(p0); } else { var f1 = frames.Next(f0); var p1 = f1.Values[offset].Float1; NetAssert.True(f1.Frame > f0.Frame); NetAssert.True(f1.Frame > frame); int f0Frame = f0.Frame; if (f0Frame < (f1.Frame - Core.RemoteSendRate * 2)) { f0Frame = f1.Frame - Core.RemoteSendRate * 2; } float t = f1.Frame - f0Frame; float d = frame - f0Frame; return(angle ? Mathf.LerpAngle(p0, p1, d / t) : Mathf.Lerp(p0, p1, d / t)); } }
public Packet Acquire() { Packet stream = null; lock (pool) { if (pool.Count > 0) { stream = pool.Pop(); } } if (stream == null) { stream = new Packet(new byte[RuntimeSettings.Instance.packetSize - 100]); stream.pool = this; } NetAssert.True(stream.isPooled); stream.isPooled = false; stream.Position = 0; stream.Size = (RuntimeSettings.Instance.packetSize - 100) << 3; return(stream); }
public override void Lost(Packet packet) { while (packet.EntityUpdates.Count > 0) { var env = packet.EntityUpdates.Dequeue(); var pending = env.Proxy.Envelopes.Dequeue(); //NetLog.Error("LOST ENV {0}, IN TRANSIT: {1}", env.Proxy, env.Proxy.Envelopes.Count); //NetAssert.Same(env.Proxy, _outgoingProxiesByNetId[env.Proxy.NetId], string.Format("PROXY MISS-MATCH {0} <> {1}", env.Proxy, _outgoingProxiesByNetId[env.Proxy.NetId])); //NetAssert.Same(env, pending, string.Format("ENVELOPE MISS-MATCH {0} <> {1}", envNumber, pendingNumber)); // copy back all priorities ApplyPropertyPriorities(env); // push skipped count up one env.Proxy.Skipped += 1; // if this was a forced sync, set flag on proxy again if (env.Flags & ProxyFlags.FORCE_SYNC) { env.Proxy.Flags |= ProxyFlags.FORCE_SYNC; } // if we failed to destroy this clear destroying flag if (env.Flags & ProxyFlags.DESTROY_PENDING) { NetAssert.True(env.Proxy.Flags & ProxyFlags.DESTROY_PENDING); env.Proxy.Flags &= ~ProxyFlags.DESTROY_PENDING; } env.Dispose(); } }
public bool QueueInput(Command cmd) { if (canQueueCommands) { NetAssert.True(HasControl); if (CommandQueue.Count < Core.Config.commandQueueSize) { cmd.ServerFrame = Core.ServerFrame; cmd.Sequence = CommandSequence = NetMath.SeqNext(CommandSequence, Command.SEQ_MASK); } else { NetLog.Error("Input queue for {0} is full", this); return(false); } CommandQueue.AddLast(cmd); return(true); } else { NetLog.Error("You can only queue commands to the host in the 'SimulateController' callback"); return(false); } }
int ExecuteCommandsFromRemote() { int commandsExecuted = 0; NetAssert.True(IsOwner); do { var it = CommandQueue.GetIterator(); while (it.Next()) { if (it.val.Flags & CommandFlags.HAS_EXECUTED) { continue; } try { ExecuteCommand(it.val, false); commandsExecuted += 1; break; } finally { it.val.Flags |= CommandFlags.SEND_STATE; } } } while (UnexecutedCommandCount() > Core.Config.commandDejitterDelay); return(commandsExecuted); }
public Scene(int index, int sequence) { NetAssert.True(index == (index & 255)); NetAssert.True(sequence == (sequence & 255)); this.Index = index & 255; this.Sequence = sequence & 255; }
public static NetworkId ReadNetworkId(this BasePacket packet) { uint connection = packet.ReadUIntVB(); uint entity = packet.ReadUIntVB(); NetAssert.True(connection != uint.MaxValue); return(new NetworkId(connection, entity)); }
public void InitRoot() { RootObjects = new List <NetworkObj>(Meta.CountObjects); Path = null; Meta.InitObject(this, this, new NetworkObj_Meta.Offsets()); NetAssert.True(RootObjects.Count == Meta.CountObjects, "RootObjects.Count == Meta.CountObjects"); }
public void InvokeOnce(Command command, CommandCallback callback, int delay) { NetAssert.True(delay > 0); if (!canQueueCallbacks) { NetLog.Error("Can only queue callbacks when commands with 'IsFirstExecution' set to true are executing"); return; } //CommandCallbacks.Add(new CommandCallbackItem { Command = command, Callback = callback, Start = -1, End = command.Number + delay, Mode = CommandCallbackModes.InvokeOnce }); }
public void InvokeRepeating(Command command, CommandCallback callback, int period) { NetAssert.True(period > 0); if (!canQueueCallbacks) { NetLog.Error("Can only queue callbacks when commands with 'IsFirstExecution' set to true are executing"); return; } //CommandCallbacks.Add(new CommandCallbackItem { Command = command, Callback = callback, Start = command.Number + 1, End = command.Number + period, Mode = CommandCallbackModes.InvokeRepeating }); }
public static void Assigned(long connectionId) { //Converts the long connection id to a unsigned integer with data loss, as well as with overflow protection //This is useful if long.MaxValue is passed to the previous System.Convert.ToInt32 uint convertedConnection = unchecked ((uint)connectionId); NetAssert.True(Core.IsClient, "Core.IsClient"); NetAssert.True(convertedConnection > 0U, "connectionId > 0U"); NetAssert.True(convertedConnection != uint.MaxValue, "connectionId != uint.MaxValue"); // verify connection id NetAssert.True(ConnectionId == uint.MaxValue, "ConnectionId == uint.MaxValue"); NetLog.Debug("Assigned id {0} from server", connectionId); ConnectionId = convertedConnection; }
int IEntitySerializer.Pack(Connection connection, Packet stream, EntityProxyEnvelope env) { int propertyCount = 0; BitSet filter = ((IEntitySerializer)this).GetFilter(connection, env.Proxy); Priority[] tempPriority = Meta.PropertiesTempPriority; Priority[] proxyPriority = env.Proxy.PropertyPriority; for (int i = 0; i < proxyPriority.Length; ++i) { NetAssert.True(proxyPriority[i].PropertyIndex == i); // if this property is set both in our filter and the proxy mask we can consider it for sending if (filter.IsSet(i) && env.Proxy.IsSet(i)) { // increment priority for this property proxyPriority[i].PropertyPriority += Meta.Properties[i].Property.PropertyPriority; proxyPriority[i].PropertyPriority = UE.Mathf.Clamp(proxyPriority[i].PropertyPriority, 0, Core.Config.maxPropertyPriority); // copy to our temp array tempPriority[propertyCount] = proxyPriority[i]; // increment temp count propertyCount += 1; } } // sort temp array based on priority Array.Sort <Priority>(tempPriority, 0, propertyCount, Priority.Comparer.Instance); // write into stream PackProperties(connection, stream, env, tempPriority, propertyCount); for (int i = 0; i < env.Written.Count; ++i) { Priority p = env.Written[i]; // clear priority for written property env.Proxy.PropertyPriority[p.PropertyIndex].PropertyPriority = 0; // clear mask for it env.Proxy.Clear(p.PropertyIndex); } return(env.Written.Count); }
public int this[NetworkProperty property] { get { #if DEBUG NetAssert.NotNull(property); NetAssert.True(OffsetObjects >= 0); NetAssert.True(OffsetObjects < Root.Meta.CountObjects); NetAssert.Same(Root.Objects[OffsetObjects], this); NetAssert.Same(Root.Objects[OffsetObjects].Meta, property.PropertyMeta); NetAssert.Same(Root.Meta.Properties[Root.Objects[OffsetObjects].OffsetProperties + property.OffsetProperties].Property, property); #endif return(this.OffsetStorage + property.OffsetStorage); } }
public static Quaternion InterpolateQuaternion(ListExtended <NetworkStorage> frames, int offset, int frame) { var f0 = frames.First; var p0 = f0.Values[offset].Quaternion; if (p0 == default(Quaternion)) { p0 = Quaternion.identity; } if ((frames.Count == 1) || (f0.Frame >= frame)) { return(p0); } else { var f1 = frames.Next(f0); var p1 = f1.Values[offset].Quaternion; if (p1 == default(Quaternion)) { p1 = Quaternion.identity; } NetAssert.True(f1.Frame > f0.Frame); NetAssert.True(f1.Frame > frame); int f0Frame = f0.Frame; if (f0Frame < (f1.Frame - Core.RemoteSendRate * 2)) { f0Frame = f1.Frame - Core.RemoteSendRate * 2; } float t = f1.Frame - f0Frame; float d = frame - f0Frame; return(Quaternion.Lerp(p0, p1, d / t)); } }
public static Vector3 InterpolateVector(ListExtended <NetworkStorage> frames, int offset, int frame, float snapLimit, ref bool snapped) { var f0 = frames.First; var p0 = f0.Values[offset].Vector3; if ((frames.Count == 1) || (f0.Frame >= frame)) { return(p0); } else { var f1 = frames.Next(f0); var p1 = f1.Values[offset].Vector3; NetAssert.True(f1.Frame > f0.Frame); NetAssert.True(f1.Frame > frame); if ((p0 - p1).sqrMagnitude > (snapLimit * snapLimit)) { snapped = true; return(p1); } else { int f0Frame = f0.Frame; if (f0Frame < (f1.Frame - Core.RemoteSendRate * 2)) { f0Frame = f1.Frame - Core.RemoteSendRate * 2; } float t = f1.Frame - f0Frame; float d = frame - f0Frame; return(Vector3.Lerp(p0, p1, d / t)); } } }
void AddPropertyToArray(int offsetProperties, int offsetObjects, NetworkProperty property) { NetAssert.Null(Properties[offsetProperties].Property); if (offsetProperties > 0) { NetAssert.NotNull(Properties[offsetProperties - 1].Property); } Properties[offsetProperties].Property = property; Properties[offsetProperties].OffsetObjects = offsetObjects; for (int i = 0; i < 32; ++i) { int f = 1 << i; // this can't be set if (Filters[i] != null) { NetAssert.False(Filters[i].IsSet(offsetProperties)); } // if property is included in this filter, flag it if ((property.PropertyFilters & f) == f) { if (Filters[i] == null) { Filters[i] = new BitSet(); } Filters[i].Set(offsetProperties); // now it must be set NetAssert.True(Filters[i].IsSet(offsetProperties)); } } }
public UniqueId(byte[] bytes) { NetAssert.NotNull(bytes); NetAssert.True(bytes.Length == 16); this = default(UniqueId); this.byte0 = bytes[0]; this.byte1 = bytes[1]; this.byte2 = bytes[2]; this.byte3 = bytes[3]; this.byte4 = bytes[4]; this.byte5 = bytes[5]; this.byte6 = bytes[6]; this.byte7 = bytes[7]; this.byte8 = bytes[8]; this.byte9 = bytes[9]; this.byte10 = bytes[10]; this.byte11 = bytes[11]; this.byte12 = bytes[12]; this.byte13 = bytes[13]; this.byte14 = bytes[14]; this.byte15 = bytes[15]; }
public override void Delivered(Packet packet) { while (packet.EntityUpdates.Count > 0) { var env = packet.EntityUpdates.Dequeue(); var pending = env.Proxy.Envelopes.Dequeue(); //NetLog.Info("DELIVERED ENV {0}, IN TRANSIT: {1}", env.Proxy, env.Proxy.Envelopes.Count); //NetAssert.Same(env.Proxy, _outgoingProxiesByNetId[env.Proxy.NetId], string.Format("PROXY MISS-MATCH {0} <> {1}", env.Proxy, _outgoingProxiesByNetId[env.Proxy.NetId])); //NetAssert.Same(env, pending, string.Format("ENVELOPE MISS-MATCH {0} <> {1}", envNumber, pendingNumber)); if (env.Flags & ProxyFlags.DESTROY_PENDING) { NetAssert.True(env.Proxy.Flags & ProxyFlags.DESTROY_PENDING); // delete proxy DestroyOutgoingProxy(env.Proxy); } else if (env.Flags & ProxyFlags.CREATE_REQUESTED) { // if this token has been sent, clear it if (ReferenceEquals(env.ControlTokenGained, env.Proxy.ControlTokenGained)) { env.Proxy.ControlTokenGained = null; } // clear out request / progress for create env.Proxy.Flags &= ~ProxyFlags.CREATE_REQUESTED; // set create done env.Proxy.Flags |= ProxyFlags.CREATE_DONE; } env.Dispose(); } }
public void Add() { NetAssert.True(OffsetObjects == Objects.Count); Objects.Add(this); }
public NetworkArray_Quaternion(int length, int stride) : base(length, stride) { NetAssert.True((stride == 1) || (stride == 2)); }
public NetworkArray_Vector(int length, int stride) : base(length, stride) { NetAssert.True((stride == 1) || (stride == 2)); }
public NetworkArray_PrefabId(int length, int stride) : base(length, stride) { NetAssert.True(stride == 1); }
void PackProperties(Connection connection, Packet packet, EntityProxyEnvelope env, Priority[] priority, int count) { int propertyCountPtr = packet.Position; packet.WriteByte(0, Meta.PacketMaxPropertiesBits); // how many bits can we write at the most int bits = System.Math.Min(Meta.PacketMaxBits, packet.Size - packet.Position); for (int i = 0; i < count; ++i) { // this means we can even fit another property id if (bits <= Meta.PropertyIdBits) { break; } // we have written enough properties if (env.Written.Count == Meta.PacketMaxProperties) { break; } Priority p = priority[i]; NetworkPropertyInfo pi = Meta.Properties[p.PropertyIndex]; if (p.PropertyPriority == 0) { break; } int b = Meta.PropertyIdBits + pi.Property.BitCount(Objects[pi.OffsetObjects]); int ptr = packet.Position; if (bits >= b) { // write property id packet.WriteInt(p.PropertyIndex, Meta.PropertyIdBits); if (pi.Property.Write(connection, Objects[pi.OffsetObjects], Storage, packet)) { #if DEBUG int totalBits = packet.Position - ptr; if (totalBits != b) { //NetLog.Warn("Property of type {0} did not write the correct amount of bits, written: {1}, expected: {2}", pi.Property, totalBits, b); } #endif if (packet.Overflowing) { packet.Position = ptr; break; } // use up bits bits -= b; // add to written list env.Written.Add(p); } else { // reset position packet.Position = ptr; } } } // gotta be less then 256 NetAssert.True(env.Written.Count <= Meta.PacketMaxProperties); // write the amount of properties Packet.WriteByteAt(packet.ByteBuffer, propertyCountPtr, Meta.PacketMaxPropertiesBits, (byte)env.Written.Count); }
public void Attach() { NetAssert.NotNull(UnityObject); NetAssert.False(IsAttached); NetAssert.True((NetworkId.Packed == 0UL) || (Source != null)); try { AttachIsRunning = true; // mark as don't destroy on load GameObject.DontDestroyOnLoad(UnityObject.gameObject); // assign network id if (Source == null) { NetworkId = NetworkIdAllocator.Allocate(); } // add to entities list Core.entitiesThawed.AddLast(this); // mark as attached Flags |= EntityFlags.ATTACHED; // call out to behaviours foreach (IEntityBehaviour eb in Behaviours) { try { if (eb.Invoke && ReferenceEquals(eb.entity, this.UnityObject)) { eb.Attached(); } } catch (Exception exn) { NetLog.Error("User code threw exception inside Attached callback"); NetLog.Exception(exn); } } // call out to user try { GlobalEventListenerBase.EntityAttachedInvoke(this.UnityObject); } catch (Exception exn) { NetLog.Error("User code threw exception inside Attached callback"); NetLog.Exception(exn); } // log NetLog.Debug("Attached {0} (Token: {1})", this, AttachToken); } finally { AttachIsRunning = false; } }
public void Detach() { NetAssert.NotNull(UnityObject); NetAssert.True(IsAttached); NetAssert.True(NetworkId.Packed != 0UL); if (AutoRemoveChildEntities) { foreach (AscensionEntity child in UnityObject.GetComponentsInChildren(typeof(AscensionEntity), true)) { if (child.IsAttached && (ReferenceEquals(child.entity, this) == false)) { child.transform.parent = null; } } } if (Controller) { RevokeControl(null); } // destroy on all connections var it = Core.connections.GetIterator(); while (it.Next()) { it.val.entityChannel.DestroyOnRemote(this); } // call out to behaviours foreach (IEntityBehaviour eb in Behaviours) { try { if (eb.Invoke && ReferenceEquals(eb.entity, this.UnityObject)) { eb.Detached(); eb.entity = null; } } catch (Exception exn) { NetLog.Error("User code threw exception inside Detach callback"); NetLog.Exception(exn); } } // call out to user try { GlobalEventListenerBase.EntityDetachedInvoke(this.UnityObject); } catch (Exception exn) { NetLog.Error("User code threw exception inside Detach callback"); NetLog.Exception(exn); } // clear out attached flag Flags &= ~EntityFlags.ATTACHED; // remove from entities list if (Core.entitiesFrozen.Contains(this)) { Core.entitiesFrozen.Remove(this); } if (Core.entitiesThawed.Contains(this)) { Core.entitiesThawed.Remove(this); } // clear from unity object UnityObject.entity = null; // log NetLog.Debug("Detached {0}", this); }
public void Simulate() { Serializer.OnSimulateBefore(); Iterator <Command> it; if (IsOwner) { foreach (IEntityBehaviour eb in Behaviours) { try { if (eb != null && ((MonoBehaviour)(object)eb) && eb.Invoke && ReferenceEquals(eb.entity, this.UnityObject)) { eb.SimulateOwner(); } } catch (Exception exn) { Debug.LogException(exn); } } } else { //FIXED: Entities getting frozen on clients after 10 seconds. //if (AscensionNetwork.IsClient) //{ // var diff = AscensionNetwork.ServerFrame - (Serializer as NetworkState).Frames.Last.Frame; // if (diff > 600) // { // Freeze(true); // } //} } if (HasControl) { NetAssert.Null(Controller); // execute all old commands (in order) it = CommandQueue.GetIterator(); while (it.Next()) { NetAssert.True(it.val.Flags & CommandFlags.HAS_EXECUTED); var resetState = ReferenceEquals(it.val, CommandQueue.First); if (resetState) { it.val.SmoothCorrection(); } // exec old command ExecuteCommand(it.val, resetState); } try { canQueueCommands = true; foreach (IEntityBehaviour eb in Behaviours) { if (eb.Invoke && ReferenceEquals(eb.entity, this.UnityObject)) { eb.SimulateController(); } } } finally { canQueueCommands = false; } // execute all new commands (in order) it = CommandQueue.GetIterator(); while (it.Next()) { if (it.val.Flags & CommandFlags.HAS_EXECUTED) { continue; } ExecuteCommand(it.val, false); } // if this is a local entity we are controlling // we should dispose all commands (there is no need to store them) if (IsOwner) { while (CommandQueue.Count > 0) { CommandQueue.RemoveFirst(); } //RemoveOldCommandCallbacks(CommandSequence); } else { //if (CommandQueue.count > 0) { // RemoveOldCommandCallbacks(CommandQueue.First.Sequence); //} } } else { if (Controller != null) { //if (CommandQueue.count > 0) { // RemoveOldCommandCallbacks(CommandQueue.First.Sequence); //} if (ExecuteCommandsFromRemote() == 0) { Command cmd = CommandQueue.LastOrDefault; for (int i = 0; i < Behaviours.Length; ++i) { if (ReferenceEquals(Behaviours[i].entity, this.UnityObject)) { Behaviours[i].MissingCommand(cmd); } } } } } Serializer.OnSimulateAfter(); }
private void PackResult(Packet packet) { foreach (EntityProxy proxy in OutgoingProxiesByNetworkId.Values) { Entity entity = proxy.Entity; // four conditions have to hold // 1) Entity must exist locally (not null) // 2) The connection must be the controller // 3) The entity must exist remotely // 4) The entity has to have unsent states if ((entity != null) && ReferenceEquals(entity.Controller, connection) && connection.entityChannel.ExistsOnRemote(entity) && EntityHasUnsentState(entity)) { NetAssert.True(entity.IsOwner); int proxyPos = packet.Position; int cmdWriteCount = 0; packet.WriteBool(true); packet.WriteNetworkId(proxy.NetworkId); var it = entity.CommandQueue.GetIterator(); while (it.Next()) { if (it.val.Flags & CommandFlags.HAS_EXECUTED) { if (it.val.Flags & CommandFlags.SEND_STATE) { int cmdPos = packet.Position; packet.WriteBool(true); packet.WriteTypeId(it.val.ResultObject.Meta.TypeId); packet.WriteUShort(it.val.Sequence, Command.SEQ_BITS); packet.WriteToken(it.val.ResultObject.Token); it.val.PackResult(connection, packet); if (packet.Overflowing) { packet.Position = cmdPos; break; } else { cmdWriteCount += 1; it.val.Flags &= ~CommandFlags.SEND_STATE; it.val.Flags |= CommandFlags.SEND_STATE_PERFORMED; } } } } // we wrote too much or nothing at all if (packet.Overflowing || (cmdWriteCount == 0)) { packet.Position = proxyPos; break; } else { // stop marker for states packet.WriteStopMarker(); } // dispose commands we dont need anymore while ((entity.CommandQueue.Count > 1) && (entity.CommandQueue.First.Flags & CommandFlags.SEND_STATE_PERFORMED)) { entity.CommandQueue.RemoveFirst().Free(); } } } // stop marker for proxies packet.WriteStopMarker(); }
void IEntitySerializer.Read(Connection connection, Packet packet, int frame) { int count = packet.ReadByte(Meta.PacketMaxPropertiesBits); var storage = default(NetworkStorage); if (Entity.HasPredictedControl) { NetAssert.True(Frames.Count == 1); storage = Frames.First; storage.Frame = Core.Frame; } else { if (Frames.First.Frame == -1) { NetAssert.True(Frames.Count == 1); storage = Frames.First; storage.Frame = frame; } else { storage = DuplicateStorage(Frames.Last); storage.Frame = frame; storage.ClearAll(); // tell the properties that need to know about this for (int i = 0; i < Meta.OnFrameCloned.Count; ++i) { // grab property info var pi = Meta.OnFrameCloned[i]; // invoke callback pi.Property.OnFrameCloned(Objects[pi.OffsetObjects], storage); } Frames.AddLast(storage); } } //fixes bug #224 //IState.SetTransforms to replace NetworkTransform.SetTransforms, //this new methods works around the issue of position snapping for //entities when their position updates are delayed. //NetworkTransform.ChangeTransform to replace the previous NetworkTransform.SetTransforms //for changing the transform target for interpolation after it's been set once. if (Entity.HasControl && !Entity.HasPredictedControl && !Entity.IsOwner) { for (int i = 0; i < Meta.Properties.Length; ++i) { var pi = Meta.Properties[i]; if (pi.Property.ToController == false) { // calculate property index int index = Objects[pi.OffsetObjects][pi.Property]; // copy value from latest frame storage.Values[index] = Frames.First.Values[index]; } } } while (--count >= 0) { var propertyIndex = packet.ReadInt(Meta.PropertyIdBits); var propertyInfo = Meta.Properties[propertyIndex]; if (!Entity.IsOwner) { EntityProxy proxy; if (Entity.Source.entityChannel.TryFindProxy(Entity, out proxy)) { proxy.PropertyPriority[propertyIndex].PropertyUpdated = frame; } } // make sure this is the correct one NetAssert.True(propertyIndex == Objects[propertyInfo.OffsetObjects].OffsetProperties + propertyInfo.Property.OffsetProperties); // read data into frame propertyInfo.Property.Read(connection, Objects[propertyInfo.OffsetObjects], storage, packet); // set changed flag storage.Set(propertyIndex); } }