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)); }
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); }
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); }
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); }
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); }
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); }
public void GenerateSlotsRange <TNewSlotType>(int startIndex, int endIndex, CollectionContext context) where TNewSlotType : TSlotType, new() { GenerateSlotsRange(typeof(TNewSlotType), startIndex, endIndex, context); }
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())); }
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)); }
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); }
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); }
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); }
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); } } }
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)); } }
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); }
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)); }
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]); } }
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); }
public override void Sort(IComparer <TElementType> comparer, CollectionContext context) { throw new System.NotImplementedException(); }
public void Expand(int expandBySlots, CollectionContext context) { Expand <TSlotType>(expandBySlots, context); }