More generics on storage system
This commit is contained in:
parent
56a45b9ad3
commit
0806794722
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
@ -56,6 +56,7 @@ public class AbstractStorageGridCell implements IStorageGridCell {
|
|||||||
detachStorage();
|
detachStorage();
|
||||||
components.remove(component1);
|
components.remove(component1);
|
||||||
attachStorage();
|
attachStorage();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user