More generics on storage system

This commit is contained in:
DBotThePony 2021-09-08 18:22:45 +07:00
parent 56a45b9ad3
commit 0806794722
Signed by: DBot
GPG Key ID: DCC23B5715498507
12 changed files with 397 additions and 369 deletions

View File

@ -31,6 +31,8 @@ import ru.dbotthepony.mc.otm.item.ItemPortableCondensationDrive;
import ru.dbotthepony.mc.otm.matter.MatterGrid; import ru.dbotthepony.mc.otm.matter.MatterGrid;
import ru.dbotthepony.mc.otm.matter.MatterRegistry; import ru.dbotthepony.mc.otm.matter.MatterRegistry;
import ru.dbotthepony.mc.otm.network.MatteryNetworking; 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.ArrayList;
import java.util.HashSet; import java.util.HashSet;
@ -165,6 +167,8 @@ public class OverdriveThatMatters {
// LOGGER.info("Registered network"); // LOGGER.info("Registered network");
MatterRegistry.registerInitialItems(); MatterRegistry.registerInitialItems();
StorageObjectRegistry.register(ItemStackObject.class, ItemStackObject.EMPTY);
} }
private void setupClient(final FMLClientSetupEvent event) { private void setupClient(final FMLClientSetupEvent event) {

View File

@ -25,7 +25,6 @@ import ru.dbotthepony.mc.otm.container.MatteryContainer;
import ru.dbotthepony.mc.otm.menu.DriveRackMenu; import ru.dbotthepony.mc.otm.menu.DriveRackMenu;
import ru.dbotthepony.mc.otm.storage.IStorageIdentity; import ru.dbotthepony.mc.otm.storage.IStorageIdentity;
import ru.dbotthepony.mc.otm.storage.ItemStackObject; import ru.dbotthepony.mc.otm.storage.ItemStackObject;
import ru.dbotthepony.mc.otm.storage.StorageItemView;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;

View File

@ -56,6 +56,7 @@ public class AbstractStorageGridCell implements IStorageGridCell {
detachStorage(); detachStorage();
components.remove(component1); components.remove(component1);
attachStorage(); attachStorage();
return;
} }
} }
} }

View File

@ -31,7 +31,6 @@ public class ItemMonitorMenu extends PoweredMatteryMenu implements INetworkedIte
view = new NetworkedItemView(inventory.player, this, tile == null); view = new NetworkedItemView(inventory.player, this, tile == null);
if (tile != null) { if (tile != null) {
OverdriveThatMatters.LOGGER.info("{}", tile.cell.getStorageGrid().global_item_view.getCombinedItemList());
view.setComponent(tile.cell.getStorageGrid().getVirtualComponent(ItemStackObject.class)); view.setComponent(tile.cell.getStorageGrid().getVirtualComponent(ItemStackObject.class));
} }

View File

@ -2,9 +2,18 @@ package ru.dbotthepony.mc.otm.storage;
import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault;
import java.math.BigDecimal; 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 @MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault @ParametersAreNonnullByDefault
public interface IStorageObject { public interface IStorageObject {
@ -14,4 +23,43 @@ public interface IStorageObject {
BigDecimal getCount(); BigDecimal getCount();
boolean isEmpty(); boolean isEmpty();
/**
* @return max stack size for this stack object
* Optional.empty() if unlimited (default)
*/
default Optional<BigDecimal> 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));
}
} }

View File

@ -3,9 +3,11 @@ package ru.dbotthepony.mc.otm.storage;
import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@MethodsReturnNonnullByDefault @MethodsReturnNonnullByDefault

View File

@ -5,6 +5,7 @@ import net.minecraft.world.item.ItemStack;
import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Optional;
@MethodsReturnNonnullByDefault @MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault @ParametersAreNonnullByDefault
@ -18,7 +19,7 @@ public record ItemStackObject(ItemStack stack) implements IStorageObject {
@Override @Override
public void setCount(BigDecimal value) { public void setCount(BigDecimal value) {
stack.setCount(Math.max(value.intValue(), 0)); setCount(value.intValue());
} }
public void setCount(int value) { public void setCount(int value) {
@ -34,8 +35,29 @@ public record ItemStackObject(ItemStack stack) implements IStorageObject {
return stack.getCount(); return stack.getCount();
} }
@Override
public Optional<BigDecimal> getMaxStackSize() {
return Optional.of(new BigDecimal(stack.getMaxStackSize()));
}
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return stack.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;
}
} }

View File

@ -15,30 +15,14 @@ import java.util.*;
@MethodsReturnNonnullByDefault @MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault @ParametersAreNonnullByDefault
public class StorageGrid { public class StorageGrid {
public final StorageItemView global_item_view = new StorageItemView();
private final HashSet<IStorageGridCell> cells = new HashSet<>(); private final HashSet<IStorageGridCell> cells = new HashSet<>();
private final HashMap<Class<? extends IStorageObject>, IStorageView<? extends IStorageObject>> views = new HashMap<>(); private final HashMap<Class<? extends IStorageObject>, VirtualComponent<? extends IStorageObject>> virtual_components = new HashMap<>();
private final HashMap<Class<? extends IStorageObject>, ArrayList<IStorageConsumer<? extends IStorageObject>>> consumers = new HashMap<>();
private final HashMap<Class<? extends IStorageObject>, IStorageComponent<? extends IStorageObject>> virtual_components = new HashMap<>();
public StorageGrid() { public StorageGrid() {}
views.put(ItemStackObject.class, global_item_view);
}
@SuppressWarnings("unchecked")
public <T extends IStorageObject> T insertObject(Class<T> type, T object, boolean simulate) { public <T extends IStorageObject> T insertObject(Class<T> type, T object, boolean simulate) {
var leftover = object; return getVirtualComponent(type).insertObject(object, simulate);
for (var consumer : consumers.computeIfAbsent(type, (k) -> new ArrayList<>())) {
leftover = ((IStorageConsumer<T>) consumer).insertObject(leftover, simulate);
if (leftover.isEmpty()) {
return leftover;
}
}
return leftover;
} }
/** /**
@ -47,70 +31,16 @@ public class StorageGrid {
* @return a virtual IStorageComponent<T>, or null if storage grid contain no view for provided type * @return a virtual IStorageComponent<T>, or null if storage grid contain no view for provided type
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Nullable public <T extends IStorageObject> VirtualComponent<T> getVirtualComponent(final Class<T> type) {
public <T extends IStorageObject> IStorageComponent<T> getVirtualComponent(final Class<T> type) { return (VirtualComponent<T>) virtual_components.computeIfAbsent(type, (k) -> new VirtualComponent<>(type));
return (IStorageComponent<T>) virtual_components.computeIfAbsent(type, (k) -> {
final var get_view = (IStorageView<T>) views.get(type);
if (get_view == null)
return null;
return new IStorageComponent<T>() {
@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<IStorageTuple<T>> getStorageObjects() {
return get_view.getStorageObjects();
}
@Override
public Class<T> storageIdentity() {
return type;
}
@Override
public boolean addListener(IStorageListener<T> listener) {
return get_view.addListener(listener);
}
@Override
public boolean removeListener(IStorageListener<T> listener) {
return get_view.removeListener(listener);
}
};
});
} }
public int size() { public int size() {
return cells.size(); return cells.size();
} }
@SuppressWarnings("unchecked")
public <T extends IStorageObject> void add(IStorageIdentity<T> identity) { public <T extends IStorageObject> void add(IStorageIdentity<T> identity) {
final var view = (IStorageView<T>) views.get(identity.storageIdentity()); getVirtualComponent(identity.storageIdentity()).add(identity);
if (view != null && identity instanceof IStorageView provider) {
((IStorageView<T>) provider).addListenerAuto((IStorageListener<T>) view);
} else if (view != null && identity instanceof IStorageTrigger provider) {
((IStorageTrigger<T>) provider).addListener((IStorageListener<T>) view);
}
if (identity instanceof IStorageConsumer provider) {
consumers.computeIfAbsent(identity.storageIdentity(), (k) -> new ArrayList<>()).add(provider);
}
} }
public boolean add(LazyOptional<IStorageGridCell> cell) { public boolean add(LazyOptional<IStorageGridCell> cell) {
@ -145,19 +75,8 @@ public class StorageGrid {
return false; return false;
} }
@SuppressWarnings("unchecked")
public <T extends IStorageObject> void remove(IStorageIdentity<T> identity) { public <T extends IStorageObject> void remove(IStorageIdentity<T> identity) {
final var view = (IStorageView<T>) views.get(identity.storageIdentity()); getVirtualComponent(identity.storageIdentity()).remove(identity);
if (view != null && identity instanceof IStorageView provider) {
((IStorageView<T>) provider).removeListenerAuto((IStorageListener<T>) view);
} else if (view != null && identity instanceof IStorageTrigger provider) {
((IStorageTrigger<T>) provider).removeListener((IStorageListener<T>) view);
}
if (identity instanceof IStorageConsumer provider) {
consumers.computeIfAbsent(identity.storageIdentity(), (k) -> new ArrayList<>()).remove(provider);
}
} }
public boolean remove(IStorageGridCell cell) { public boolean remove(IStorageGridCell cell) {

View File

@ -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<ItemStackObject>, IStorageView<ItemStackObject> {
public record LocalTuple(ItemStackObject object, UUID id, List<RemoteTuple> tuples) implements IStorageTuple<ItemStackObject> {
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<ItemStackObject> storageIdentity() {
return ItemStackObject.class;
}
public record RemoteTuple(ItemStack stack, UUID remote_id, IStorageView<ItemStackObject> provider, LocalTuple local) {
public static final RemoteTuple EMPTY = new RemoteTuple(ItemStack.EMPTY, new UUID(0, 0), new IStorageView<>() {
@Override
public Class<ItemStackObject> storageIdentity() {
return ItemStackObject.class;
}
@Override
public boolean addListener(IStorageListener<ItemStackObject> listener) {
return false;
}
@Override
public boolean removeListener(IStorageListener<ItemStackObject> 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<IStorageTuple<ItemStackObject>> 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<UUID, RemoteTuple> indexed_by_remote_uuid = new HashMap<>();
// локальный UUID -> Локальный кортеж
private final HashMap<UUID, LocalTuple> indexed_by_local_uuid = new HashMap<>();
// Item -> Список Локальных кортежей
private final HashMap<Item, ArrayList<LocalTuple>> indexed_by_item = new HashMap<>();
public List<ItemStack> getCopyRawItemList() {
final var list = new ArrayList<ItemStack>(indexed_by_remote_uuid.size());
for (var state : indexed_by_remote_uuid.values())
list.add(state.stack.copy());
return list;
}
public List<ItemStack> getRawItemList() {
final var list = new ArrayList<ItemStack>(indexed_by_remote_uuid.size());
for (var state : indexed_by_remote_uuid.values())
list.add(state.stack);
return list;
}
@Override
public List<IStorageTuple<ItemStackObject>> getStorageObjects() {
int capacity = 0;
for (var list : indexed_by_item.values())
capacity += list.size();
final var output = new ArrayList<IStorageTuple<ItemStackObject>>(capacity);
for (var listing : indexed_by_item.values())
output.addAll(listing);
return output;
}
public List<ItemStack> getCombinedItemList() {
int capacity = 0;
for (var list : indexed_by_item.values())
capacity += list.size();
final var output = new ArrayList<ItemStack>(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<ItemStackObject> 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<IStorageListener<ItemStackObject>> listeners = new HashSet<>();
@Override
public boolean addListener(IStorageListener<ItemStackObject> listener) {
return listeners.add(listener);
}
@Override
public boolean removeListener(IStorageListener<ItemStackObject> 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;
}
}

View File

@ -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<Class<? extends IStorageObject>, StorageObjectTuple<? extends IStorageObject>> REGISTRY = new HashMap<>();
public static <T extends IStorageObject> boolean register(Class<T> 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 <T extends IStorageObject> StorageObjectTuple<T> get(Class<T> identity) {
return (StorageObjectTuple<T>) REGISTRY.get(identity);
}
public static <T extends IStorageObject> StorageObjectTuple<T> getOrError(Class<T> identity) {
return Objects.requireNonNull(get(identity), "No storage mapping present for " + identity.toString());
}
}

View File

@ -0,0 +1,16 @@
package ru.dbotthepony.mc.otm.storage;
public record StorageObjectTuple<T extends IStorageObject>(Class<T> 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();
}
}

View File

@ -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<T extends IStorageObject> implements IStorageComponent<T>, IStorageListener<T> {
public record LocalTuple<T extends IStorageObject>(T object, UUID id, List<RemoteTuple<T>> tuples) implements IStorageTuple<T> {
@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 extends IStorageObject>(T object, UUID remote_id, IStorageView<T> provider, LocalTuple<T> 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<T> identity;
@Override
public Class<T> storageIdentity() {
return identity.identity();
}
public VirtualComponent(Class<T> identity) {
this.identity = StorageObjectRegistry.getOrError(identity);
}
public static <T extends IStorageObject> Supplier<VirtualComponent<T>> factory(Class<T> identity) {
return () -> new VirtualComponent<>(identity);
}
// удаленный UUID -> Кортеж
protected final HashMap<UUID, RemoteTuple<T>> indexed_by_remote_uuid = new HashMap<>();
// локальный UUID -> Локальный кортеж
protected final HashMap<UUID, LocalTuple<T>> indexed_by_local_uuid = new HashMap<>();
// Хеш ключ -> Список Локальных кортежей
protected final HashMap<Object, ArrayList<LocalTuple<T>>> partitions = new HashMap<>();
// ArrayList для скорости работы
protected final ArrayList<IStorageListener<T>> listeners = new ArrayList<>();
protected final ArrayList<IStorageConsumer<T>> consumers = new ArrayList<>();
public void add(IStorageIdentity<T> identity) {
if (identity instanceof IStorageView<T> provider) {
provider.addListenerAuto(this);
} else if (identity instanceof IStorageTrigger<T> provider) {
provider.addListener(this);
}
if (identity instanceof IStorageConsumer<T> provider && !consumers.contains(provider)) {
consumers.add(provider);
}
}
public void remove(IStorageIdentity<T> identity) {
if (identity instanceof IStorageView<T> provider) {
provider.removeListenerAuto(this);
} else if (identity instanceof IStorageTrigger<T> provider) {
provider.removeListener(this);
}
if (identity instanceof IStorageConsumer<T> 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<T> listener) {
if (!listeners.contains(listener)) {
listeners.add(listener);
return true;
}
return false;
}
@Override
public boolean removeListener(IStorageListener<T> 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<IStorageTuple<T>> getStorageObjects() {
int capacity = 0;
for (var list : partitions.values())
capacity += list.size();
final var output = new ArrayList<IStorageTuple<T>>(capacity);
for (var listing : partitions.values())
output.addAll(listing);
return output;
}
@Override
@SuppressWarnings("unchecked")
public void addObject(T stack, UUID id, IStorageView<T> 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<T> 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);
}
}
}
}