public virtual void RemoveAt(int index)
        {
            if (index < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(index), index,
                                                      "The position must be greater than or equal to zero.");
            }

            if (index >= m_AtomList.Count)
            {
                lastOp = null;
                return;
            }

            BeforeRemoveItem(index);

            var op = new KSEQOperation <T>
            {
                replicaId = replicaId,
                realTime  = GetWallTime(),
                op        = KSEQOperationTypes.Remove,
                id        = m_AtomList[index].id
            };

            Apply(op);
            lastOp = op;
        }
        public void Insert(int index, T item)
        {
            if (index < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(index), index,
                                                      "The position must be greater than or equal to zero");
            }

            BeforeInsertItem(ref item);

            var beforeId = index - 1 < m_AtomList.Count && index - 1 >= 0 ? m_AtomList[index - 1].id : null;
            var afterId  = index < m_AtomList.Count && index >= 0 ? m_AtomList[index].id : null;
            var id       = m_IdentGenerator.GetIdent(replicaId, ++time, beforeId, afterId);
            var op       = new KSEQOperation <T>
            {
                id        = id,
                op        = KSEQOperationTypes.Insert,
                realTime  = GetWallTime(),
                value     = item,
                replicaId = replicaId
            };

            Apply(op);
            lastOp = op;
        }
        public void SendReplicationOp(string gameId, KSEQOperation <Record> op)
        {
            var game = m_Host.games[gameId];

            game.replicatedData.Apply(op);

            OthersInGame(gameId).SendAsync(nameof(NetworkedClientPeer.ReceiveReplicationOp), gameId, op).Start();
        }
        public void Apply(KSEQOperation <T>?op)
        {
            if (op == null)
            {
                return;
            }

            Apply(op.Value);
        }
예제 #5
0
 public override T this[int index]
 {
     get => base[index];
     set
     {
         SetId(ref value);
         var beforeId = index - 1 < m_AtomList.Count && index - 1 >= 0 ? m_AtomList[index - 1].id : null;
         var afterId  = index < m_AtomList.Count && index >= 0 ? m_AtomList[index].id : null;
         var id       = m_IdentGenerator.GetIdent(replicaId, ++time, beforeId, afterId);
         var op       = new KSEQOperation <T>
         {
             id        = id,
             op        = KSEQOperationTypes.Set,
             realTime  = GetWallTime(),
             replicaId = replicaId,
             value     = value
         };
         lastOp = op;
         Apply(op, false);
     }
 }
예제 #6
0
        public override ApplicationResult Apply(KSEQOperation <T> op, bool quiet = false)
        {
            // Retrieve the newest queued set for the given ID if the item already exists, performing an insert with its
            // id iif the id is later, and using the appropriate value.
            if (op.op == KSEQOperationTypes.Insert &&
                m_QueuedSet.ContainsKey(op.value.id))
            {
                var replacementOp = m_QueuedSet[op.value.id];
                if (op.id < replacementOp.id)
                {
                    throw new ArgumentException(
                              $"op {op} has an ident with precedence over a set but matching id {op.value.id}", nameof(op));
                }

                m_QueuedSet.Remove(op.value.id);
                op.value = replacementOp.value;
                op.id    = replacementOp.id;
            }

            var handled = base.Apply(op, quiet);

            if (handled.applied)
            {
                switch (op.op)
                {
                // Worst case: O(N) (op to the beginning of the list
                // Best case: O(1) (op to the end of the list)
                case KSEQOperationTypes.Insert:
                    m_IdToIndex[op.value.id] = handled.index;
                    for (var i = handled.index + 1; i < Count; i++)
                    {
                        m_IdToIndex[m_AtomList[i].value.id] = i;
                    }

                    break;

                case KSEQOperationTypes.Remove:
                    m_IdToIndex.Remove(op.value.id);
                    m_RemovedIds.Remove(op.value.id);
                    for (var i = handled.index; i < Count; i++)
                    {
                        m_IdToIndex[m_AtomList[i].value.id] = i;
                    }

                    break;
                }

                return(handled);
            }

            switch (op.op)
            {
            case KSEQOperationTypes.Set:
                if (m_RemovedIds.Contains(op.value.id) || m_Removed.Contains(op.id))
                {
                    break;
                }

                // Find an existing record. Otherwise, queue up
                if (m_IdToIndex.ContainsKey(op.value.id))
                {
                    var i             = m_IdToIndex[op.value.id];
                    var atom          = m_AtomList[i];
                    var originalValue = atom.value;
                    if (originalValue.id != op.value.id)
                    {
                        throw new ArgumentException($"invalid set, {op.value.id}!={originalValue.id}");
                    }

                    // If this is later (last write first), go ahead and replace the atom with the new value.
                    // Otherwise, do nothing.
                    if (op.id < atom.id)
                    {
                        var oldAtom = atom;
                        atom.id    = op.id;
                        atom.value = op.value;
                        Remove(oldAtom.id);
                        var j = Add(op.id, op.value);
                        for (var k = Math.Min(i, j); k <= Math.Max(i, j); k++)
                        {
                            m_IdToIndex[m_AtomList[k].value.id] = k;
                        }

                        if (!quiet)
                        {
                            var collectionReplaceEvent = new CollectionReplaceEvent <T>(i, originalValue, op.value);
                            collectionReplace?.OnNext(
                                collectionReplaceEvent);

                            if (i != j)
                            {
                                collectionMove?.OnNext(new CollectionMoveEvent <T>(i, j, op.value));
                            }

                            if (op.replicaId == replicaId)
                            {
                                collectionThisReplicaReplaced?.OnNext(collectionReplaceEvent);
                            }
                        }
                    }


                    return(new ApplicationResult()
                    {
                        applied = true,
                        index = i
                    });
                }
                else
                {
                    if (!m_QueuedSet.ContainsKey(op.value.id) ||
                        (m_QueuedSet.ContainsKey(op.value.id) && op.id < m_QueuedSet[op.value.id].id))
                    {
                        // Only replace the existing queued set if this op is later
                        m_QueuedSet[op.value.id] = op;
                    }

                    return(new ApplicationResult()
                    {
                        applied = true
                    });
                }
            }

            return(new ApplicationResult());
        }
        public virtual ApplicationResult Apply(KSEQOperation <T> op, bool quiet = false)
        {
            switch (op.op)
            {
            case KSEQOperationTypes.Insert:
                if (m_Removed.Contains(op.id))
                {
                    break;
                }

                var indexAdded = Add(op.id, op.value);
                if (!quiet)
                {
                    var collectionAddEvent = new CollectionAddEvent <T>(indexAdded, op.value);
                    collectionAdd?.OnNext(collectionAddEvent);
                    countChanged?.OnNext(m_AtomList.Count);
                    if (op.replicaId == replicaId)
                    {
                        collectionThisReplicaAdded?.OnNext(collectionAddEvent);
                    }
                }

                return(new ApplicationResult()
                {
                    applied = true,
                    index = indexAdded
                });

            case KSEQOperationTypes.Remove:
                if (m_Removed.Contains(op.id))
                {
                    break;
                }

                m_Removed.Add(op.id);
                var indexRemoved = IndexOf(op.id);
                if (indexRemoved < 0)
                {
                    break;
                }

                var oldValue = m_AtomList[indexRemoved];
                m_AtomList.RemoveAt(indexRemoved);
                if (!quiet)
                {
                    var collectionRemoveEvent = new CollectionRemoveEvent <T>(indexRemoved, oldValue.value);
                    collectionRemove?.OnNext(collectionRemoveEvent);
                    countChanged?.OnNext(m_AtomList.Count);
                    if (op.replicaId == replicaId)
                    {
                        collectionThisReplicaRemoved?.OnNext(collectionRemoveEvent);
                    }
                }

                return(new ApplicationResult()
                {
                    applied = true,
                    index = indexRemoved
                });
            }

            return(new ApplicationResult()
            {
            });
        }
 public void ReceiveReplicationOp(string gameId, KSEQOperation <Record> op)
 {
     ((ReplicatedGameContext)games.First(g => g.gameId == gameId)).replicatedData.Apply(op);
 }