コード例 #1
0
        public virtual Result <int> GetCanAddAmount(TElementType item, int bailEarly, CollectionContext context)
        {
            if (isReadOnly)
            {
                return(new Result <int>(0, Errors.CollectionIsReadOnly));
            }

            // Handle restrictions here once to avoid doing it on every CanSet call
            var canAdd = CheckAddRestriction(item, context);

            if (canAdd.result == false)
            {
                return(new Result <int>(0, canAdd.error));
            }

            // Avoid chaning the caller's context.
            context = context.Clone();
            context.validationFlags &= ~(CollectionContext.Validations.Restrictions | CollectionContext.Validations.SpecificInstance);

            int totalAmount = 0;

            for (int i = 0; i < slots.Length; i++)
            {
                if (CanSet(i, item, item.maxStackSize, context).result)
                {
                    if (IsNull(slots[i].item))
                    {
                        totalAmount += item.maxStackSize;
                    }
                    else if (AreEqual(slots[i].item, item))
                    {
                        totalAmount += item.maxStackSize - slots[i].amount;
                    }
                }

                if (totalAmount >= bailEarly)
                {
                    break;
                }
            }

            return(new Result <int>(totalAmount));
        }
コード例 #2
0
        public virtual Result <CollectionRemoveResult <TElementType> > Remove(TElementType item, int amount, CollectionContext context)
        {
            var canRemove = CanRemove(item, amount, context);

            if (canRemove.result == false)
            {
                return(new Result <CollectionRemoveResult <TElementType> >(null, canRemove.error));
            }

            // First try to remove the ReferenceEquals item instance
            var indices        = IndexOfAll(o => ReferenceEquals(o, item));
            var affectedSlots  = new List <SlotAmountItem <TElementType> >();
            var amountToRemove = amount;

            amountToRemove = RemoveFromIndices(indices, amountToRemove, affectedSlots, context);

            if (amountToRemove > 0)
            {
                indices        = IndexOfAll(item);
                amountToRemove = RemoveFromIndices(indices, amountToRemove, affectedSlots, context);
            }

            var response = new Result <CollectionRemoveResult <TElementType> >(new CollectionRemoveResult <TElementType>(affectedSlots.ToArray()));

            if (context.HasFlag(CollectionContext.Events.Remove))
            {
                InvokeOnRemovedItem(response.result);
            }

            if (context.HasFlag(CollectionContext.Events.SlotChanged))
            {
                InvokeOnSlotsChanged(new CollectionSlotsChangedResult(response.result.affectedSlots.Select(o => o.slot).ToArray()));
            }

            return(response);
        }
コード例 #3
0
        protected int RemoveFromIndices(IEnumerable <int> indices, int amountToRemove, List <SlotAmountItem <TElementType> > affectedSlots, CollectionContext context)
        {
            foreach (int index in indices)
            {
                var currentItem = slots[index].item;
                if (amountToRemove - slots[index].amount < 0)
                {
                    // Just remove moveAmount, there's enough in this slot to complete
                    SetInternal(index, currentItem, slots[index].amount - amountToRemove, context);
                    affectedSlots.Add(new SlotAmountItem <TElementType>(index, amountToRemove, currentItem));
                    amountToRemove = 0;
                    break;
                }

                affectedSlots.Add(new SlotAmountItem <TElementType>(index, slots[index].amount, currentItem));
                amountToRemove -= slots[index].amount;
                SetInternal(index, default(TElementType), 0, context);
            }

            return(amountToRemove);
        }
コード例 #4
0
        protected Result <bool> CanSet2Collections(ICollection fromCol, int fromIndex, ICollection toCol, int toIndex, object fromItem, int fromAmount, object toItem, int toAmount, CollectionContext context)
        {
            if (fromCol == toCol)
            {
                // Staying in the same collection, avoid firing events
                context.validationFlags &= ~CollectionContext.Validations.SpecificInstance;
            }

            context.originalIndex = toIndex;
            var canSet2 = fromCol.CanSetBoxed(fromIndex, fromItem, fromAmount, context);

            context.originalIndex = fromIndex;
            var canSet1 = toCol.CanSetBoxed(toIndex, toItem, toAmount, context);

            if (canSet1.result == false)
            {
                return(canSet1);
            }

            if (canSet2.result == false)
            {
                return(canSet2);
            }

            return(true);
        }
コード例 #5
0
        protected Result <bool> Set2Collections(ICollection fromCol, int fromIndex, ICollection toCol, int toIndex, object fromItem, int fromAmount, object toItem, int toAmount, CollectionContext context)
        {
            // Create a clone to avoid ovewriting settings
            context = context.Clone();
            var fireEventsBefore = context.HasFlag(CollectionContext.Events.SlotChanged);

            if (fromCol == toCol)
            {
                // Staying in the same collection, avoid firing add / remove events
                context.fireEventFlags   = 0;
                context.validationFlags &= ~CollectionContext.Validations.SpecificInstance;
            }

            var can = CanSet2Collections(fromCol, fromIndex, toCol, toIndex, fromItem, fromAmount, toItem, toAmount, context);

            if (can.result == false)
            {
                return(can);
            }

            context.originalIndex = toIndex;
            fromCol.SetBoxed(fromIndex, fromItem, fromAmount, context);
            context.originalIndex = fromIndex;
            toCol.SetBoxed(toIndex, toItem, toAmount, context);

            if (fireEventsBefore && fromCol == toCol)
            {
                InvokeOnSlotsChanged(new CollectionSlotsChangedResult(new int[] { fromIndex, toIndex }));
            }

            return(true);
        }
コード例 #6
0
        public override Result <int> GetCanAddAmount(TElementType item, int bailEarly, CollectionContext context)
        {
            if (isReadOnly)
            {
                return(new Result <int>(0, Errors.CollectionIsReadOnly));
            }

            var canAddRestriction = CheckAddRestriction(item, context);

            if (canAddRestriction.result == false)
            {
                return(new Result <int>(0, canAddRestriction.error));
            }

            // Avoid chaning the caller's context.
            context = context.Clone();
            context.allowAABBCollisionSelf = false;
            context.validationFlags       &= ~(CollectionContext.Validations.Restrictions | CollectionContext.Validations.SpecificInstance);

            // Keep track of which slots get 'occupied' when we set our item in a new slot (The item might occupy multiple slots).
            int addAmount    = 0;
            var occupiedList = new BitArray(slotCount);

            for (int i = 0; i < slotCount; i++)
            {
                if (CanSet(i, item, item.maxStackSize, context).result)
                {
                    if (IsNull(slots[i].item) && occupiedList[i] == false && CanSetAABB(i, item, 1, context))
                    {
                        for (int x = 0; x < item.layoutShape.convexX; x++)
                        {
                            for (int y = 0; y < item.layoutShape.convexY; y++)
                            {
                                occupiedList[i + Offset(x, y)] = true;
                            }
                        }

                        addAmount += item.maxStackSize;
                    }
                    else if (AreEqual(slots[i].item, item) && occupiedList[i] == false)
                    {
                        var canAdd = item.maxStackSize - slots[i].amount;
                        addAmount += canAdd;
                    }
                }

                if (addAmount >= bailEarly)
                {
                    break;
                }
            }

            return(addAmount);
        }
コード例 #7
0
 public void GenerateSlotsRange <TNewSlotType>(int startIndex, int endIndex, CollectionContext context)
     where TNewSlotType : TSlotType, new()
 {
     GenerateSlotsRange(typeof(TNewSlotType), startIndex, endIndex, context);
 }
コード例 #8
0
        public Result <CollectionRemoveResult <object> > RemoveBoxed(object item, int amount = 1, CollectionContext context = null)
        {
            if (item is TElementType == false && item != null)
            {
                return(new Result <CollectionRemoveResult <object> >(null, Errors.CollectionInvalidItemType));
            }

            var remove = Remove((TElementType)item, amount, context);

            if (remove.error != null)
            {
                return(new Result <CollectionRemoveResult <object> >(new CollectionRemoveResult <object>(new SlotAmountItem <object> [0]), remove.error));
            }

            var arr = remove.result.affectedSlots.Select(o => new SlotAmountItem <object>(o.slot, o.amount, o.item));

            return(new CollectionRemoveResult <object>(arr.ToArray()));
        }
コード例 #9
0
        public virtual Result <bool> SwapOrMerge(int fromIndex, ICollection toCol, int toIndex, int mergeAmount, CollectionContext context = null)
        {
            if (isReadOnly)
            {
                return(new Result <bool>(false, Errors.CollectionIsReadOnly));
            }

            context = context ?? new CollectionContext();
            var merged = Merge(fromIndex, toCol, toIndex, mergeAmount, context);

            if (merged.result)
            {
                return(merged);
            }

            var swapped = Swap(fromIndex, toCol, toIndex, context);

            if (swapped.result)
            {
                return(swapped);
            }

            return(new Result <bool>(false, Errors.CollectionCanNotMoveItem));
        }
コード例 #10
0
        public virtual Result <bool> CanSet(int index, TElementType item, int amount, CollectionContext context)
        {
            if (isReadOnly)
            {
                return(new Result <bool>(false, Errors.CollectionIsReadOnly));
            }

            if (amount < 0)
            {
                return(new Result <bool>(false, Errors.CollectionDoesNotContainItem));
            }

            if (amount > item?.maxStackSize)
            {
                return(new Result <bool>(false, Errors.ItemIsExceedingMaxStackSize));
            }

            // Slot is empty and item is empty, ignore call
            if (slots[index].isOccupied == false && IsNull(item))
            {
                return(true);
            }

            Error error         = null;
            var   currentAmount = GetAmount(index);

            if (AreEqual(slots[index].item, item))
            {
                // Adding or removing

                if (amount < currentAmount)
                {
                    error = CheckRemoveRestriction(item, context).error;
                }
                else if (amount > currentAmount)
                {
                    error = CheckAddRestriction(item, context).error;

                    if (context.HasFlag(CollectionContext.Validations.SpecificInstance) && IsNull(item) == false)
                    {
                        var i = IndexOfSpecificInstance(item);
                        if (i != -1 && index != i)
                        {
                            error = Errors.CollectionAlreadyContainsSpecificInstance;
                        }
                    }
                }
                else
                {
                    // Equal item, same amount
                    return(true);
                }
            }
            else
            {
                // Swapping out item

                if (IsNull(item))
                {
                    error = CheckRemoveRestriction(item, context).error;
                }
                else
                {
                    if (IsNull(slots[index].item) == false)
                    {
                        error = CheckRemoveRestriction(item, context).error;
                        if (error != null)
                        {
                            return(new Result <bool>(false, error));
                        }
                    }

                    error = CheckAddRestriction(item, context).error;

                    if (context.HasFlag(CollectionContext.Validations.SpecificInstance) && IsNull(item) == false)
                    {
                        var i = IndexOfSpecificInstance(item);
                        if (i != -1 && index != i)
                        {
                            error = Errors.CollectionAlreadyContainsSpecificInstance;
                        }
                    }
                }
            }

            if (error != null)
            {
                return(new Result <bool>(false, error));
            }

            return(true);
        }
コード例 #11
0
        public virtual Result <bool> Set(int index, TElementType item, int amount, CollectionContext context)
        {
            var canSet = CanSet(index, item, amount, context);

            if (canSet.result == false)
            {
                return(canSet);
            }

            // Slot is empty and item is empty, ignore call
            if (slots[index].isOccupied == false && IsNull(item))
            {
                return(true);
            }

            if (AreEqual(slots[index].item, item) && GetAmount(index) == amount)
            {
                // Still set for *SpecificInstance
                SetInternal(index, item, amount, context);
                return(true);
            }

            var currentItem   = slots[index].item;
            var currentAmount = GetAmount(index);

            if (AreEqual(slots[index].item, item))
            {
                SetInternal(index, item, amount, context);

                var diff = Math.Abs(amount - currentAmount);
                if (amount < currentAmount)
                {
                    if (context.HasFlag(CollectionContext.Events.Remove))
                    {
                        InvokeOnRemovedItem(new CollectionRemoveResult <TElementType>(new [] { new SlotAmountItem <TElementType>(index, diff, currentItem) }));
                    }
                }
                else if (amount > currentAmount)
                {
                    if (context.HasFlag(CollectionContext.Events.Add))
                    {
                        InvokeOnAddedItem(new CollectionAddResult(new SlotAmount[] { new SlotAmount(index, diff) }));
                    }
                }
            }
            else
            {
                if (IsNull(item))
                {
                    SetInternal(index, item, 0, context);
                    if (context.HasFlag(CollectionContext.Events.Remove))
                    {
                        InvokeOnRemovedItem(new CollectionRemoveResult <TElementType>(new [] { new SlotAmountItem <TElementType>(index, currentAmount, currentItem) }));
                    }
                }
                else
                {
                    if (IsNull(currentItem) == false)
                    {
                        SetInternal(index, default(TElementType), 0, context);
                        if (context.HasFlag(CollectionContext.Events.Remove))
                        {
                            InvokeOnRemovedItem(new CollectionRemoveResult <TElementType>(new [] { new SlotAmountItem <TElementType>(index, currentAmount, currentItem) }));
                        }
                    }

                    // Add the item
                    SetInternal(index, item, amount, context);
                    if (context.HasFlag(CollectionContext.Events.Add))
                    {
                        InvokeOnAddedItem(new CollectionAddResult(new [] { new SlotAmount(index, amount) }));
                    }
                }
            }

            if (context.HasFlag(CollectionContext.Events.SlotChanged))
            {
                InvokeOnSlotsChanged(new CollectionSlotsChangedResult(new int[] { index }));
            }

            return(true);
        }
コード例 #12
0
        public virtual Result <CollectionAddResult> Add(TElementType item, int amount, CollectionContext context)
        {
            var canAdd = CanAdd(item, amount, context);

            if (canAdd.result == false)
            {
                return(new Result <CollectionAddResult>(null, canAdd.error));
            }

            var totalAddAmount = amount;
            var affectedSlots  = new List <SlotAmount>();

            // Can Add already checks all restrictions and requirements.
            var contextClone = context.Clone();

            contextClone.validationFlags &= ~CollectionContext.Validations.Restrictions;
            contextClone.validationFlags &= ~CollectionContext.Validations.SpecificInstance;

            // We'll handle events ourselves and bundle them
            contextClone.fireEventFlags = 0;

            var enumerator = GetAddItemEnumerator(item, amount, contextClone);

            while (enumerator.MoveNext())
            {
                var index = enumerator.Current;

                // Slot is occupied by other slot.
                if (slots[index].isOccupied && IsNull(slots[index].item))
                {
                    continue;
                }

                var isEmptySlot         = this[index] == null;
                var canAddToStackAmount = Math.Min(item.maxStackSize - GetAmount(index), totalAddAmount);
                totalAddAmount -= canAddToStackAmount;
                affectedSlots.Add(new SlotAmount(index, canAddToStackAmount));

                if (totalAddAmount > 0)
                {
                    // Need to do another stack placement after this one.
                    if (isEmptySlot)
                    {
                        // Empty slot, so set the reference (and make a clone later if we need to place more).
                        SetInternal(index, item, GetAmount(index) + canAddToStackAmount, contextClone);
                    }
                    else
                    {
                        // If we're adding to an existing stack and still have to place more keep the existing item in the slot and just increase the amount.
                        SetInternal(index, this[index], GetAmount(index) + canAddToStackAmount, contextClone);
                    }
                }
                else
                {
                    // We don't want to place any more after this iteration.
                    SetInternal(index, item, GetAmount(index) + canAddToStackAmount, contextClone);
                }

                if (totalAddAmount <= 0)
                {
                    break;
                }

                if (isEmptySlot)
                {
                    item = CreateElementClone(item); // The next item we're placing needs to be a new instance.
                }
            }

            logger.LogVerbose($"Added {item} x {amount} to collection", this);
            var response = new Result <CollectionAddResult>(new CollectionAddResult(affectedSlots.ToArray()));

            if (context.HasFlag(CollectionContext.Events.Add))
            {
                InvokeOnAddedItem(response.result);
            }

            if (context.HasFlag(CollectionContext.Events.SlotChanged))
            {
                InvokeOnSlotsChanged(new CollectionSlotsChangedResult(response.result.affectedSlots.Select(o => o.slot).ToArray()));
            }

            return(response);
        }
コード例 #13
0
        public virtual IEnumerator <int> GetAddItemEnumerator(TElementType item, int amount, CollectionContext context)
        {
            context.validationFlags &= ~CollectionContext.Validations.SpecificInstance;
            context.validationFlags &= ~CollectionContext.Validations.Restrictions;

            // First try stacking
            for (int i = 0; i < slots.Length; i++)
            {
                if (AreEqual(slots[i].item, item))
                {
                    var canAddAmount = Math.Min(item.maxStackSize - GetAmount(i), amount);
                    if (canAddAmount > 0 && CanSet(i, item, GetAmount(i) + canAddAmount, context).result)
                    {
                        yield return(i);
                    }
                }
            }

            // Then try finding a new empty slot
            for (var i = 0; i < slots.Length; i++)
            {
                var canAddAmount = Math.Min(item.maxStackSize, amount);
                if (slots[i].isOccupied == false && CanSet(i, item, canAddAmount, context).result)
                {
                    yield return(i);
                }
            }
        }
コード例 #14
0
        public virtual void GenerateSlotsRange(Type slotType, int startIndex, int endIndex, CollectionContext context)
        {
            if (slotType.IsSubclassOf(typeof(TSlotType)) == false && typeof(TSlotType) != slotType)
            {
                throw new ArgumentException($"Given type {slotType.Name} is not a subclass of {typeof(TSlotType).Name}");
            }

            if (isReadOnly)
            {
                return;
            }

            int[] changedSlots = new int[endIndex - startIndex + 1];
            for (int i = startIndex; i <= endIndex; i++)
            {
                var inst = (TSlotType)Activator.CreateInstance(slotType, new object[0]);
                inst.collection = this;
                inst.index      = i;

                slots[i] = inst;
                changedSlots[i - startIndex] = i;
            }

            if (context.HasFlag(CollectionContext.Events.SlotChanged))
            {
                InvokeOnSlotsChanged(new CollectionSlotsChangedResult(changedSlots));
            }
        }
コード例 #15
0
        public override Result <bool> CanSet(int index, TElementType item, int amount, CollectionContext context)
        {
            var canSet = base.CanSet(index, item, amount, context);

            if (canSet.result == false)
            {
                return(canSet);
            }

            if (CanSetAABB(index, item, amount, context) == false)
            {
                return(new Result <bool>(false, Errors.LayoutCollectionItemBlocked));
            }

            return(true);
        }
コード例 #16
0
        protected Result <bool> Merge(int fromIndex, ICollection toCol, int toIndex, int amount, CollectionContext context = null)
        {
            if (isReadOnly)
            {
                return(new Result <bool>(false, Errors.CollectionIsReadOnly));
            }

            context = context ?? new CollectionContext();
            if (this == toCol && fromIndex == toIndex)
            {
                // Target is same as source
                return(true);
            }

            if (this[fromIndex] == null)
            {
                // Merging nothing
                return(true);
            }

            if (amount > GetAmount(fromIndex))
            {
                return(new Result <bool>(false, Errors.CollectionDoesNotContainItem));
            }

            if (toCol.GetBoxed(toIndex) == null || AreEqual(this[fromIndex], toCol.GetBoxed(toIndex) as TElementType))
            {
                if (amount < GetAmount(fromIndex))
                {
                    // Not moving entire stack
                    var toItem = toCol.GetBoxed(toIndex);
                    if (toItem == null)
                    {
                        // Moving to an empty slot, we need to duplicate our item to avoid storing the safe ref twice.
                        toItem = CreateElementClone(this[fromIndex]);
                    }

                    return(Set2Collections(this, fromIndex, toCol, toIndex, this[fromIndex], GetAmount(fromIndex) - amount, toItem, toCol.GetAmount(toIndex) + amount, context));
                }
                else if (amount == GetAmount(fromIndex))
                {
                    // Moving entire stack
                    return(Set2Collections(this, fromIndex, toCol, toIndex, default(TElementType), 0, this[fromIndex], GetAmount(fromIndex) + toCol.GetAmount(toIndex), context));
                }
            }

            return(new Result <bool>(false, Errors.ItemsAreNotEqual));
        }
コード例 #17
0
        protected override void SetInternal(int index, TElementType item, int amount, CollectionContext context)
        {
            SetAABB(index, slots[index].item, null); // Clear the old
            base.SetInternal(index, item, amount, context);

            if (IsNull(slots[index].item) == false)
            {
                SetAABB(index, item, slots[index]);
            }
        }
コード例 #18
0
        public virtual Result <bool> MoveAuto(int fromIndex, ICollection toCol, int moveAmount, CollectionContext context = null)
        {
            if (isReadOnly)
            {
                return(new Result <bool>(false, Errors.CollectionIsReadOnly));
            }

            if (moveAmount > GetAmount(fromIndex))
            {
                return(new Result <bool>(false, Errors.CollectionDoesNotContainItem));
            }

            if (this == toCol)
            {
                // Moving to a new auto. slot inside the same collection
                return(true);
            }

            context = context ?? new CollectionContext();
            var canAdd = toCol.CanAddBoxed(this[fromIndex], moveAmount, context);

            if (canAdd.result == false)
            {
                return(canAdd);
            }

            var canSet = CanSet(fromIndex, this[fromIndex], GetAmount(fromIndex) - moveAmount);

            if (canSet.result == false)
            {
                return(canSet);
            }

            var moveItem = this[fromIndex];

            if (moveAmount < GetAmount(fromIndex))
            {
                // Not moving entire stack, clone object.
                moveItem = CreateElementClone(this[fromIndex]);
            }

            toCol.AddBoxed(moveItem, moveAmount);
            Set(fromIndex, this[fromIndex], GetAmount(fromIndex) - moveAmount, context);

            return(true);
        }
コード例 #19
0
 public override void Sort(IComparer <TElementType> comparer, CollectionContext context)
 {
     throw new System.NotImplementedException();
 }
コード例 #20
0
 public void Expand(int expandBySlots, CollectionContext context)
 {
     Expand <TSlotType>(expandBySlots, context);
 }