diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index ae8bc074f..e8ef8ff7c 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -31,6 +31,8 @@ import ru.dbotthepony.mc.otm.item.ItemPortableCondensationDrive; import ru.dbotthepony.mc.otm.matter.MatterGrid; import ru.dbotthepony.mc.otm.matter.MatterRegistry; import ru.dbotthepony.mc.otm.network.MatteryNetworking; +import ru.dbotthepony.mc.otm.storage.ItemStackObject; +import ru.dbotthepony.mc.otm.storage.StorageObjectRegistry; import java.util.ArrayList; import java.util.HashSet; @@ -165,6 +167,8 @@ public class OverdriveThatMatters { // LOGGER.info("Registered network"); MatterRegistry.registerInitialItems(); + + StorageObjectRegistry.register(ItemStackObject.class, ItemStackObject.EMPTY); } private void setupClient(final FMLClientSetupEvent event) { diff --git a/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityDriveRack.java b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityDriveRack.java index 4afd60783..04fe01edf 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityDriveRack.java +++ b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityDriveRack.java @@ -25,7 +25,6 @@ import ru.dbotthepony.mc.otm.container.MatteryContainer; import ru.dbotthepony.mc.otm.menu.DriveRackMenu; import ru.dbotthepony.mc.otm.storage.IStorageIdentity; import ru.dbotthepony.mc.otm.storage.ItemStackObject; -import ru.dbotthepony.mc.otm.storage.StorageItemView; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/AbstractStorageGridCell.java b/src/main/java/ru/dbotthepony/mc/otm/capability/AbstractStorageGridCell.java index e6f5c285d..b27ba122c 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/capability/AbstractStorageGridCell.java +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/AbstractStorageGridCell.java @@ -56,6 +56,7 @@ public class AbstractStorageGridCell implements IStorageGridCell { detachStorage(); components.remove(component1); attachStorage(); + return; } } } diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/ItemMonitorMenu.java b/src/main/java/ru/dbotthepony/mc/otm/menu/ItemMonitorMenu.java index 0e6acfe18..06ac80bd7 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/menu/ItemMonitorMenu.java +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/ItemMonitorMenu.java @@ -31,7 +31,6 @@ public class ItemMonitorMenu extends PoweredMatteryMenu implements INetworkedIte view = new NetworkedItemView(inventory.player, this, tile == null); if (tile != null) { - OverdriveThatMatters.LOGGER.info("{}", tile.cell.getStorageGrid().global_item_view.getCombinedItemList()); view.setComponent(tile.cell.getStorageGrid().getVirtualComponent(ItemStackObject.class)); } diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageObject.java b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageObject.java index cfbf71482..bab226d51 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageObject.java +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageObject.java @@ -2,9 +2,18 @@ package ru.dbotthepony.mc.otm.storage; import net.minecraft.MethodsReturnNonnullByDefault; +import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import java.math.BigDecimal; +import java.util.Optional; +/** + * Interface implements all methods required for storage system functioning and interaction + * with your kind of items/objects/whatever. + * + * You can either implement this interface directly (hard dependency, discouraged, unless you do direct addon to this storage system) + * or create a record class (preferred), which encapsulate your storage object. + */ @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault public interface IStorageObject { @@ -14,4 +23,43 @@ public interface IStorageObject { BigDecimal getCount(); boolean isEmpty(); + + /** + * @return max stack size for this stack object + * Optional.empty() if unlimited (default) + */ + default Optional getMaxStackSize() { + return Optional.empty(); + } + + /** + * @return Identity utilized to partition view table (if it has any). + * Defaults to no partitioning; meaning performance will degrade much quicker than it should. + * Good object is an object that will influence on sameItem() return result, making it return true. + * It is weakly considered that if !this.itemIdentity().equals(other.itemIdentity()); + * then this.sameItem(other) will always return false. + * + * If implemented, and storage is also partitioned properly, then performance will be flat equally to view table's performance. + * + * Example: ItemStack#getItem() + */ + default Object partitionKey() { + return Object.class; + }; + + /** + * @param other other object to compare; if not the same of this object type just return false + * @return boolean representing whenever internal state of this equals to other; + * this include tags, mapping IDs, capabilities, excluding amount; + * behavior is pretty much the same as ItemStack.isSameItemSameTags + */ + boolean sameItem(IStorageObject other); + + default void grow(BigDecimal amount) { + setCount(getCount().add(amount)); + } + + default void shrink(BigDecimal amount) { + setCount(getCount().subtract(amount)); + } } diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageView.java b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageView.java index d77cb8e79..ea1d2cc47 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageView.java +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageView.java @@ -3,9 +3,11 @@ package ru.dbotthepony.mc.otm.storage; import net.minecraft.MethodsReturnNonnullByDefault; +import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import java.math.BigDecimal; import java.util.List; +import java.util.Optional; import java.util.UUID; @MethodsReturnNonnullByDefault diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/ItemStackObject.java b/src/main/java/ru/dbotthepony/mc/otm/storage/ItemStackObject.java index fa2143eec..abcf8cdba 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/storage/ItemStackObject.java +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/ItemStackObject.java @@ -5,6 +5,7 @@ import net.minecraft.world.item.ItemStack; import javax.annotation.ParametersAreNonnullByDefault; import java.math.BigDecimal; +import java.util.Optional; @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault @@ -18,7 +19,7 @@ public record ItemStackObject(ItemStack stack) implements IStorageObject { @Override public void setCount(BigDecimal value) { - stack.setCount(Math.max(value.intValue(), 0)); + setCount(value.intValue()); } public void setCount(int value) { @@ -34,8 +35,29 @@ public record ItemStackObject(ItemStack stack) implements IStorageObject { return stack.getCount(); } + @Override + public Optional getMaxStackSize() { + return Optional.of(new BigDecimal(stack.getMaxStackSize())); + } + @Override public boolean isEmpty() { return stack.isEmpty(); } + + @Override + public Object partitionKey() { + return stack.getItem(); + } + + @Override + public boolean sameItem(IStorageObject other) { + if (other == this) + return true; + + if (other instanceof ItemStackObject obj) + return ItemStack.isSameItemSameTags(stack, obj.stack); + + return false; + } } diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/StorageGrid.java b/src/main/java/ru/dbotthepony/mc/otm/storage/StorageGrid.java index 96521b02e..3ab8719bc 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/storage/StorageGrid.java +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/StorageGrid.java @@ -15,30 +15,14 @@ import java.util.*; @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault public class StorageGrid { - public final StorageItemView global_item_view = new StorageItemView(); private final HashSet cells = new HashSet<>(); - private final HashMap, IStorageView> views = new HashMap<>(); - private final HashMap, ArrayList>> consumers = new HashMap<>(); - private final HashMap, IStorageComponent> virtual_components = new HashMap<>(); + private final HashMap, VirtualComponent> virtual_components = new HashMap<>(); - public StorageGrid() { - views.put(ItemStackObject.class, global_item_view); - } + public StorageGrid() {} - @SuppressWarnings("unchecked") public T insertObject(Class type, T object, boolean simulate) { - var leftover = object; - - for (var consumer : consumers.computeIfAbsent(type, (k) -> new ArrayList<>())) { - leftover = ((IStorageConsumer) consumer).insertObject(leftover, simulate); - - if (leftover.isEmpty()) { - return leftover; - } - } - - return leftover; + return getVirtualComponent(type).insertObject(object, simulate); } /** @@ -47,70 +31,16 @@ public class StorageGrid { * @return a virtual IStorageComponent, or null if storage grid contain no view for provided type */ @SuppressWarnings("unchecked") - @Nullable - public IStorageComponent getVirtualComponent(final Class type) { - return (IStorageComponent) virtual_components.computeIfAbsent(type, (k) -> { - final var get_view = (IStorageView) views.get(type); - - if (get_view == null) - return null; - - return new IStorageComponent() { - @Override - public T insertObject(T obj, boolean simulate) { - return StorageGrid.this.insertObject(type, obj, simulate); - } - - @Override - public T getStoredObject(UUID id) { - return get_view.getStoredObject(id); - } - - @Override - public T extractObject(UUID id, BigDecimal amount, boolean simulate) { - return get_view.extractObject(id, amount, simulate); - } - - @Override - public List> getStorageObjects() { - return get_view.getStorageObjects(); - } - - @Override - public Class storageIdentity() { - return type; - } - - @Override - public boolean addListener(IStorageListener listener) { - return get_view.addListener(listener); - } - - @Override - public boolean removeListener(IStorageListener listener) { - return get_view.removeListener(listener); - } - }; - }); + public VirtualComponent getVirtualComponent(final Class type) { + return (VirtualComponent) virtual_components.computeIfAbsent(type, (k) -> new VirtualComponent<>(type)); } public int size() { return cells.size(); } - @SuppressWarnings("unchecked") public void add(IStorageIdentity identity) { - final var view = (IStorageView) views.get(identity.storageIdentity()); - - if (view != null && identity instanceof IStorageView provider) { - ((IStorageView) provider).addListenerAuto((IStorageListener) view); - } else if (view != null && identity instanceof IStorageTrigger provider) { - ((IStorageTrigger) provider).addListener((IStorageListener) view); - } - - if (identity instanceof IStorageConsumer provider) { - consumers.computeIfAbsent(identity.storageIdentity(), (k) -> new ArrayList<>()).add(provider); - } + getVirtualComponent(identity.storageIdentity()).add(identity); } public boolean add(LazyOptional cell) { @@ -145,19 +75,8 @@ public class StorageGrid { return false; } - @SuppressWarnings("unchecked") public void remove(IStorageIdentity identity) { - final var view = (IStorageView) views.get(identity.storageIdentity()); - - if (view != null && identity instanceof IStorageView provider) { - ((IStorageView) provider).removeListenerAuto((IStorageListener) view); - } else if (view != null && identity instanceof IStorageTrigger provider) { - ((IStorageTrigger) provider).removeListener((IStorageListener) view); - } - - if (identity instanceof IStorageConsumer provider) { - consumers.computeIfAbsent(identity.storageIdentity(), (k) -> new ArrayList<>()).remove(provider); - } + getVirtualComponent(identity.storageIdentity()).remove(identity); } public boolean remove(IStorageGridCell cell) { diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/StorageItemView.java b/src/main/java/ru/dbotthepony/mc/otm/storage/StorageItemView.java deleted file mode 100644 index f85b74b28..000000000 --- a/src/main/java/ru/dbotthepony/mc/otm/storage/StorageItemView.java +++ /dev/null @@ -1,278 +0,0 @@ -package ru.dbotthepony.mc.otm.storage; - -import net.minecraft.MethodsReturnNonnullByDefault; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import ru.dbotthepony.mc.otm.OverdriveThatMatters; - -import javax.annotation.ParametersAreNonnullByDefault; -import java.math.BigDecimal; -import java.util.*; - -@MethodsReturnNonnullByDefault -@ParametersAreNonnullByDefault -public class StorageItemView implements IStorageListener, IStorageView { - public record LocalTuple(ItemStackObject object, UUID id, List tuples) implements IStorageTuple { - public static final LocalTuple EMPTY = new LocalTuple(ItemStackObject.EMPTY, new UUID(0, 0), List.of()); - - @Override - public boolean equals(Object obj) { - return obj instanceof LocalTuple tuple && tuple.id.equals(id) || obj instanceof UUID id && this.id.equals(id); - } - - @Override - public int hashCode() { - return id.hashCode(); - } - } - - @Override - public Class storageIdentity() { - return ItemStackObject.class; - } - - public record RemoteTuple(ItemStack stack, UUID remote_id, IStorageView provider, LocalTuple local) { - public static final RemoteTuple EMPTY = new RemoteTuple(ItemStack.EMPTY, new UUID(0, 0), new IStorageView<>() { - @Override - public Class storageIdentity() { - return ItemStackObject.class; - } - - @Override - public boolean addListener(IStorageListener listener) { - return false; - } - - @Override - public boolean removeListener(IStorageListener listener) { - return false; - } - - @Override - public ItemStackObject getStoredObject(UUID id) { - return ItemStackObject.EMPTY; - } - - @Override - public ItemStackObject extractObject(UUID id, BigDecimal amount, boolean simulate) { - return ItemStackObject.EMPTY; - } - - @Override - public List> getStorageObjects() { - return List.of(); - } - }, LocalTuple.EMPTY); - - public ItemStackObject extract(BigDecimal amount, boolean simulate) { - return provider.extractObject(remote_id, amount, simulate); - } - - public BigDecimal extractCount(BigDecimal amount, boolean simulate) { - return provider.extractObjectCount(remote_id, amount, simulate); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof RemoteTuple tuple && tuple.remote_id.equals(remote_id) || obj instanceof UUID id && id.equals(remote_id); - } - - @Override - public int hashCode() { - return remote_id.hashCode(); - } - } - - // удаленный UUID -> Кортеж - private final HashMap indexed_by_remote_uuid = new HashMap<>(); - - // локальный UUID -> Локальный кортеж - private final HashMap indexed_by_local_uuid = new HashMap<>(); - - // Item -> Список Локальных кортежей - private final HashMap> indexed_by_item = new HashMap<>(); - - public List getCopyRawItemList() { - final var list = new ArrayList(indexed_by_remote_uuid.size()); - - for (var state : indexed_by_remote_uuid.values()) - list.add(state.stack.copy()); - - return list; - } - - public List getRawItemList() { - final var list = new ArrayList(indexed_by_remote_uuid.size()); - - for (var state : indexed_by_remote_uuid.values()) - list.add(state.stack); - - return list; - } - - @Override - public List> getStorageObjects() { - int capacity = 0; - - for (var list : indexed_by_item.values()) - capacity += list.size(); - - final var output = new ArrayList>(capacity); - - for (var listing : indexed_by_item.values()) - output.addAll(listing); - - return output; - } - - public List getCombinedItemList() { - int capacity = 0; - - for (var list : indexed_by_item.values()) - capacity += list.size(); - - final var output = new ArrayList(capacity); - - for (var listing : indexed_by_item.values()) - for (var combined : listing) - output.add(combined.object.stack()); - - return output; - } - - @Override - public void addObject(ItemStackObject _stack, UUID id, IStorageView provider) { - if (indexed_by_remote_uuid.containsKey(id)) { - throw new IllegalStateException("Already tracking tuple with id " + id); - } - - var stack = _stack.stack(); - - var items = indexed_by_item.computeIfAbsent(stack.getItem(), (k) -> new ArrayList<>()); - LocalTuple local_tuple = null; - - for (var item : items) { - if (ItemStack.tagMatches(item.object.stack(), stack)) { - item.object.stack().grow(stack.getCount()); - local_tuple = item; - break; - } - } - - boolean added = local_tuple == null; - - if (added) { - local_tuple = new LocalTuple((ItemStackObject) _stack.copy(), UUID.randomUUID(), new ArrayList<>(1)); - items.add(local_tuple); - indexed_by_local_uuid.put(local_tuple.id, local_tuple); - } - - var tuple = new RemoteTuple(stack.copy(), id, provider, local_tuple); - local_tuple.tuples.add(tuple); - indexed_by_remote_uuid.put(id, tuple); - - if (added) { - for (var listener : listeners) { - listener.addObject(local_tuple.object, local_tuple.id, this); - } - } else { - for (var listener : listeners) { - listener.changeObject(local_tuple.id, local_tuple.object.getCount()); - } - } - } - - @Override - public void changeObject(UUID id, BigDecimal new_count) { - assert new_count.compareTo(BigDecimal.ZERO) > 0; - var tuple = indexed_by_remote_uuid.get(id); - - if (tuple == null) - throw new IllegalStateException("No such tuple with id " + id); - - var delta = new_count.intValue() - tuple.stack.getCount(); - tuple.stack.setCount(new_count.intValue()); - tuple.local.object.stack().grow(delta); - - for (var listener : listeners) { - listener.changeObject(tuple.local.id, tuple.local.object.getCount()); - } - } - - @Override - public void removeObject(UUID id) { - var tuple = indexed_by_remote_uuid.get(id); - - if (tuple == null) - throw new IllegalStateException("No such tuple with id " + id); - - var item = tuple.local.object.stack().getItem(); - tuple.local.object.stack().shrink(tuple.stack.getCount()); - tuple.local.tuples.remove(tuple); - - indexed_by_remote_uuid.remove(id); - - if (tuple.local.object.stack().getCount() <= 0 || tuple.local.tuples.size() == 0) { - assert tuple.local.object.stack().getCount() == tuple.local.tuples.size(); - - indexed_by_local_uuid.remove(tuple.local.id); - indexed_by_item.get(item).remove(tuple.local); - - for (var listener : listeners) { - listener.removeObject(tuple.local.id); - } - } - } - - private final Set> listeners = new HashSet<>(); - - @Override - public boolean addListener(IStorageListener listener) { - return listeners.add(listener); - } - - @Override - public boolean removeListener(IStorageListener listener) { - return listeners.remove(listener); - } - - @Override - public ItemStackObject getStoredObject(UUID id) { - return indexed_by_local_uuid.getOrDefault(id, LocalTuple.EMPTY).object; - } - - @Override - public ItemStackObject extractObject(UUID id, BigDecimal _amount, boolean simulate) { - var tuple = indexed_by_local_uuid.get(id); - - if (tuple == null) - return ItemStackObject.EMPTY; - - int amount = _amount.intValue(); - - if (amount == 0) - return ItemStackObject.EMPTY; - - if (amount == -1) - amount = tuple.object.stack().getMaxStackSize(); - - int extract = Math.min(amount, tuple.object.stack().getCount()); - int extracted = 0; - - var copy = tuple.object.stack().copy(); - - for (var remote_tuple : tuple.tuples) { - extracted += remote_tuple.extractCount(new BigDecimal(extract - extracted), simulate).intValue(); - - if (extracted >= extract) - break; - } - - if (extracted > 0) { - copy.setCount(extracted); - return new ItemStackObject(copy); - } - - return ItemStackObject.EMPTY; - } -} diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/StorageObjectRegistry.java b/src/main/java/ru/dbotthepony/mc/otm/storage/StorageObjectRegistry.java new file mode 100644 index 000000000..fe2d6deaa --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/StorageObjectRegistry.java @@ -0,0 +1,25 @@ +package ru.dbotthepony.mc.otm.storage; + +import java.util.HashMap; +import java.util.Objects; + +public class StorageObjectRegistry { + private static final HashMap, StorageObjectTuple> REGISTRY = new HashMap<>(); + + public static boolean register(Class identity, T empty) { + final var tuple = new StorageObjectTuple<>(identity, empty); + final boolean removed = REGISTRY.remove(identity) != null; + REGISTRY.put(identity, tuple); + + return removed; + } + + @SuppressWarnings("unchecked") + public static StorageObjectTuple get(Class identity) { + return (StorageObjectTuple) REGISTRY.get(identity); + } + + public static StorageObjectTuple getOrError(Class identity) { + return Objects.requireNonNull(get(identity), "No storage mapping present for " + identity.toString()); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/StorageObjectTuple.java b/src/main/java/ru/dbotthepony/mc/otm/storage/StorageObjectTuple.java new file mode 100644 index 000000000..c4d0d81da --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/StorageObjectTuple.java @@ -0,0 +1,16 @@ +package ru.dbotthepony.mc.otm.storage; + +public record StorageObjectTuple(Class identity, T empty) { + @Override + public boolean equals(Object obj) { + if (obj instanceof StorageObjectTuple tuple) + return tuple.identity == identity; + + return false; + } + + @Override + public int hashCode() { + return identity.hashCode(); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/VirtualComponent.java b/src/main/java/ru/dbotthepony/mc/otm/storage/VirtualComponent.java new file mode 100644 index 000000000..2f257a65e --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/VirtualComponent.java @@ -0,0 +1,271 @@ +package ru.dbotthepony.mc.otm.storage; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; +import java.util.function.Supplier; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public class VirtualComponent implements IStorageComponent, IStorageListener { + public record LocalTuple(T object, UUID id, List> tuples) implements IStorageTuple { + @Override + public boolean equals(Object obj) { + return obj instanceof LocalTuple tuple && tuple.id.equals(id) || obj instanceof UUID id && this.id.equals(id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + } + + public record RemoteTuple(T object, UUID remote_id, IStorageView provider, LocalTuple local) { + public T extract(BigDecimal amount, boolean simulate) { + return provider.extractObject(remote_id, amount, simulate); + } + + public BigDecimal extractCount(BigDecimal amount, boolean simulate) { + return provider.extractObjectCount(remote_id, amount, simulate); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof RemoteTuple tuple && tuple.remote_id.equals(remote_id) || obj instanceof UUID id && id.equals(remote_id); + } + + @Override + public int hashCode() { + return remote_id.hashCode(); + } + } + + protected final StorageObjectTuple identity; + + @Override + public Class storageIdentity() { + return identity.identity(); + } + + public VirtualComponent(Class identity) { + this.identity = StorageObjectRegistry.getOrError(identity); + } + + public static Supplier> factory(Class identity) { + return () -> new VirtualComponent<>(identity); + } + + // удаленный UUID -> Кортеж + protected final HashMap> indexed_by_remote_uuid = new HashMap<>(); + + // локальный UUID -> Локальный кортеж + protected final HashMap> indexed_by_local_uuid = new HashMap<>(); + + // Хеш ключ -> Список Локальных кортежей + protected final HashMap>> partitions = new HashMap<>(); + + // ArrayList для скорости работы + protected final ArrayList> listeners = new ArrayList<>(); + protected final ArrayList> consumers = new ArrayList<>(); + + public void add(IStorageIdentity identity) { + if (identity instanceof IStorageView provider) { + provider.addListenerAuto(this); + } else if (identity instanceof IStorageTrigger provider) { + provider.addListener(this); + } + + if (identity instanceof IStorageConsumer provider && !consumers.contains(provider)) { + consumers.add(provider); + } + } + + public void remove(IStorageIdentity identity) { + if (identity instanceof IStorageView provider) { + provider.removeListenerAuto(this); + } else if (identity instanceof IStorageTrigger provider) { + provider.removeListener(this); + } + + if (identity instanceof IStorageConsumer provider) { + consumers.remove(provider); + } + } + + @Override + public T insertObject(T object, boolean simulate) { + var leftover = object; + + for (var consumer : consumers) { + leftover = consumer.insertObject(leftover, simulate); + + if (leftover.isEmpty()) { + return leftover; + } + } + + return leftover; + } + + @Override + public boolean addListener(IStorageListener listener) { + if (!listeners.contains(listener)) { + listeners.add(listener); + return true; + } + + return false; + } + + @Override + public boolean removeListener(IStorageListener listener) { + return listeners.remove(listener); + } + + @Override + public T getStoredObject(UUID id) { + final var tuple = indexed_by_local_uuid.get(id); + return tuple != null ? tuple.object : identity.empty(); + } + + public static final BigDecimal MINUS_ONE = new BigDecimal(-1); + + @Override + @SuppressWarnings("unchecked") + public T extractObject(UUID id, BigDecimal amount, boolean simulate) { + var tuple = indexed_by_local_uuid.get(id); + + if (tuple == null || amount.compareTo(BigDecimal.ZERO) == 0) + return identity.empty(); + + if (amount.compareTo(MINUS_ONE) <= 0) + amount = tuple.object.getMaxStackSize().orElse(tuple.object.getCount()); + + BigDecimal extract = tuple.object.getCount().min(amount); + BigDecimal extracted = BigDecimal.ZERO; + + final var copy = (T) tuple.object.copy(); + + for (var remote_tuple : tuple.tuples) { + extracted = extracted.add(remote_tuple.extractCount(extract.subtract(extracted), simulate)); + + if (extracted.compareTo(extract) >= 0) + break; + } + + if (extracted.compareTo(BigDecimal.ZERO) > 0) { + copy.setCount(extracted); + return copy; + } + + return identity.empty(); + } + + @Override + public List> getStorageObjects() { + int capacity = 0; + + for (var list : partitions.values()) + capacity += list.size(); + + final var output = new ArrayList>(capacity); + + for (var listing : partitions.values()) + output.addAll(listing); + + return output; + } + + @Override + @SuppressWarnings("unchecked") + public void addObject(T stack, UUID id, IStorageView provider) { + if (indexed_by_remote_uuid.containsKey(id)) { + throw new IllegalStateException("Already tracking tuple with id " + id); + } + + var items = partitions.computeIfAbsent(stack.partitionKey(), (k) -> new ArrayList<>()); + LocalTuple local_tuple = null; + + for (var item : items) { + if (item.object.sameItem(stack)) { + item.object.grow(stack.getCount()); + local_tuple = item; + break; + } + } + + boolean added = local_tuple == null; + + if (added) { + local_tuple = new LocalTuple<>((T) stack.copy(), UUID.randomUUID(), new ArrayList<>(1)); + items.add(local_tuple); + indexed_by_local_uuid.put(local_tuple.id, local_tuple); + } + + var tuple = new RemoteTuple<>((T) stack.copy(), id, provider, local_tuple); + local_tuple.tuples.add(tuple); + indexed_by_remote_uuid.put(id, tuple); + + if (added) { + for (var listener : listeners) { + listener.addObject(local_tuple.object, local_tuple.id, this); + } + } else { + for (var listener : listeners) { + listener.changeObject(local_tuple.id, local_tuple.object.getCount()); + } + } + } + + + @Override + public void changeObject(UUID id, BigDecimal new_count) { + assert new_count.compareTo(BigDecimal.ZERO) > 0; + var tuple = indexed_by_remote_uuid.get(id); + + if (tuple == null) + throw new IllegalStateException("No such tuple with id " + id); + + final var diff = new_count.subtract(tuple.object.getCount()); + tuple.object.setCount(new_count); + tuple.local.object.grow(diff); + + for (var listener : listeners) { + listener.changeObject(tuple.local.id, tuple.local.object.getCount()); + } + } + + @Override + public void removeObject(UUID id) { + var tuple = indexed_by_remote_uuid.get(id); + + if (tuple == null) + throw new IllegalStateException("No such tuple with id " + id); + + final var item = tuple.local.object.partitionKey(); + tuple.local.object.shrink(tuple.object.getCount()); + tuple.local.tuples.remove(tuple); + + indexed_by_remote_uuid.remove(id); + + final boolean a = tuple.local.object.getCount().compareTo(BigDecimal.ZERO) <= 0; + final boolean b = tuple.local.tuples.size() == 0; + + if (a || b) { + if (!(a && b)) + throw new IllegalStateException("View object is empty, but tuple list is not!"); + + indexed_by_local_uuid.remove(tuple.local.id); + partitions.get(item).remove(tuple.local); + + for (var listener : listeners) { + listener.removeObject(tuple.local.id); + } + } + } +}