VirtualComponent on kotlin, PoweredVirtualComponent, powered storage system

This commit is contained in:
DBotThePony 2022-01-02 12:55:35 +07:00
parent 9261562de7
commit 0ed841ab21
Signed by: DBot
GPG Key ID: DCC23B5715498507
15 changed files with 452 additions and 298 deletions

View File

@ -27,6 +27,7 @@ import ru.dbotthepony.mc.otm.capability.android.AndroidCapabilityPlayer;
import ru.dbotthepony.mc.otm.capability.drive.DrivePool;
import ru.dbotthepony.mc.otm.client.AndroidGui;
import ru.dbotthepony.mc.otm.client.EventHandler;
import ru.dbotthepony.mc.otm.core.Fraction;
import ru.dbotthepony.mc.otm.item.ItemPortableCondensationDrive;
import ru.dbotthepony.mc.otm.matter.MatterRegistry;
import ru.dbotthepony.mc.otm.network.MatteryNetworking;
@ -165,7 +166,7 @@ public class OverdriveThatMatters {
MatterRegistry.registerInitialItems();
StorageObjectRegistry.register(ItemStackWrapper.class, ItemStackWrapper.EMPTY);
StorageObjectRegistry.register(ItemStackWrapper.class, ItemStackWrapper.EMPTY, new Fraction("3.125"));
}
private void setupClient(final FMLClientSetupEvent event) {

View File

@ -25,16 +25,16 @@ public class ItemMonitorScreen extends MatteryScreen<ItemMonitorMenu> {
protected FramePanel makeMainFrame() {
var frame = new FramePanel(this, null, 0, 0, FRAME_WIDTH, FRAME_HEIGHT, getTitle());
var grid = new GridPanel(this, frame, 0, 0, 0, 0, GRID_WIDTH, GRID_HEIGHT);
grid.setDock(Dock.FILL);
grid.setDockMargin(2, 2, 2, 2);
var scroll_bar = new ScrollBarPanel(this, frame, 0, 0, 0);
scroll_bar.setDock(Dock.RIGHT);
scroll_bar.setupRowMultiplier(() -> {
return menu.view.getItems().size() / GRID_WIDTH;
});
var grid = new GridPanel(this, frame, 0, 0, GRID_WIDTH * 18, 0, GRID_WIDTH, GRID_HEIGHT);
grid.setDock(Dock.RIGHT);
grid.setDockMargin(2, 2, 2, 2);
for (int i = 0; i < GRID_WIDTH * GRID_HEIGHT; i++) {
final int index = i;

View File

@ -16,7 +16,6 @@ import ru.dbotthepony.mc.otm.storage.*;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -77,6 +76,11 @@ public class NetworkedItemView implements IStorageListener<ItemStackWrapper> {
provider.addListenerAuto(this);
}
public void removed() {
if (this.provider != null)
this.provider.removeListenerAuto(this);
}
public record NetworkedItem(int id, ItemStack stack, @Nullable UUID id_upstream) {
public NetworkedItem(int id, ItemStack stack) {
this(id, stack, null);
@ -100,8 +104,8 @@ public class NetworkedItemView implements IStorageListener<ItemStackWrapper> {
}
@Override
public void changeObject(UUID id, Fraction new_count) {
changeObject(id, new_count.toInt());
public void changeObject(UUID id, Fraction newCount) {
changeObject(id, newCount.toInt());
}
@Override

View File

@ -1,6 +1,7 @@
package ru.dbotthepony.mc.otm.storage;
import net.minecraft.MethodsReturnNonnullByDefault;
import ru.dbotthepony.mc.otm.core.Fraction;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -13,8 +14,8 @@ import java.util.Objects;
public class StorageObjectRegistry {
private static final HashMap<Class<? extends IStorageStack>, StorageObjectTuple<? extends IStorageStack>> REGISTRY = new HashMap<>();
public static <T extends IStorageStack> StorageObjectTuple<T> register(Class<T> identity, T empty) {
final var tuple = new StorageObjectTuple<>(identity, empty);
public static <T extends IStorageStack> StorageObjectTuple<T> register(Class<T> identity, T empty, Fraction energyPerOperation) {
final var tuple = new StorageObjectTuple<>(identity, empty, energyPerOperation);
REGISTRY.put(identity, tuple);
return tuple;
}

View File

@ -1,6 +1,10 @@
package ru.dbotthepony.mc.otm.storage;
public record StorageObjectTuple<T extends IStorageStack>(Class<T> identity, T empty) {
import ru.dbotthepony.mc.otm.core.Fraction;
import javax.annotation.Nonnull;
public record StorageObjectTuple<T extends IStorageStack>(@Nonnull Class<T> identity, @Nonnull T empty, @Nonnull Fraction energyPerOperation) {
@Override
public boolean equals(Object obj) {
if (obj instanceof StorageObjectTuple tuple)

View File

@ -1,270 +0,0 @@
package ru.dbotthepony.mc.otm.storage;
import net.minecraft.MethodsReturnNonnullByDefault;
import ru.dbotthepony.mc.otm.core.Fraction;
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 IStorageStack> implements IStorageComponent<T>, IStorageListener<T> {
public record LocalTuple<T extends IStorageStack>(T stack, 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 IStorageStack>(T object, UUID remote_id, IStorageView<T> provider, LocalTuple<T> local) {
public T extract(Fraction amount, boolean simulate) {
return provider.extractStack(remote_id, amount, simulate);
}
public Fraction extractCount(Fraction amount, boolean simulate) {
return provider.extractStackCount(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 IStorageStack> 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 insertStack(T stack, boolean simulate) {
var leftover = stack;
for (var consumer : consumers) {
leftover = consumer.insertStack(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 getStack(UUID id) {
final var tuple = indexed_by_local_uuid.get(id);
return tuple != null ? tuple.stack : identity.empty();
}
@Override
@SuppressWarnings("unchecked")
public T extractStack(UUID id, Fraction amount, boolean simulate) {
var tuple = indexed_by_local_uuid.get(id);
if (tuple == null || amount.compareTo(Fraction.ZERO) == 0)
return identity.empty();
if (amount.compareTo(Fraction.MINUS_ONE) <= 0)
amount = tuple.stack.getMaxStackSize().orElse(tuple.stack.getCount());
var extract = tuple.stack.getCount().min(amount);
var extracted = Fraction.ZERO;
final var copy = (T) tuple.stack.copy();
for (var remote_tuple : tuple.tuples) {
extracted = extracted.plus(remote_tuple.extractCount(extract.minus(extracted), simulate));
if (extracted.compareTo(extract) >= 0)
break;
}
if (extracted.compareTo(Fraction.ZERO) > 0) {
copy.setCount(extracted);
return copy;
}
return identity.empty();
}
@Override
public List<IStorageTuple<T>> getStacks() {
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.stack.sameItem(stack)) {
item.stack.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.stack, local_tuple.id, this);
}
} else {
for (var listener : listeners) {
listener.changeObject(local_tuple.id, local_tuple.stack.getCount());
}
}
}
@Override
public void changeObject(UUID id, Fraction new_count) {
assert new_count.compareTo(Fraction.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.minus(tuple.object.getCount());
tuple.object.setCount(new_count);
tuple.local.stack.grow(diff);
for (var listener : listeners) {
listener.changeObject(tuple.local.id, tuple.local.stack.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.stack.partitionKey();
tuple.local.stack.shrink(tuple.object.getCount());
tuple.local.tuples.remove(tuple);
indexed_by_remote_uuid.remove(id);
final boolean a = tuple.local.stack.getCount().compareTo(Fraction.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);
}
}
}
}

View File

@ -3,11 +3,15 @@ package ru.dbotthepony.mc.otm.block
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape
import ru.dbotthepony.mc.otm.Registry
import ru.dbotthepony.mc.otm.block.entity.BlockEntityDriveRack
import ru.dbotthepony.mc.otm.shapes.BlockShapes
@ -16,6 +20,17 @@ class BlockDriveRack : BlockMatteryRotatable(), EntityBlock {
return BlockEntityDriveRack(blockPos, blockState)
}
override fun <T : BlockEntity?> getTicker(
p_153212_: Level,
p_153213_: BlockState,
p_153214_: BlockEntityType<T>
): BlockEntityTicker<T>? {
if (p_153214_ != Registry.BlockEntities.DRIVE_RACK || p_153212_.isClientSide)
return null
return BlockEntityTicker { _, _, _, tile -> if (tile is BlockEntityDriveRack) tile.tick() }
}
override fun getShape(
p_60555_: BlockState,
p_60556_: BlockGetter,

View File

@ -3,11 +3,16 @@ package ru.dbotthepony.mc.otm.block
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape
import ru.dbotthepony.mc.otm.Registry
import ru.dbotthepony.mc.otm.block.entity.BlockEntityDriveRack
import ru.dbotthepony.mc.otm.block.entity.BlockEntityItemMonitor
import ru.dbotthepony.mc.otm.shapes.BlockShapes
@ -16,6 +21,17 @@ class BlockItemMonitor : BlockMatteryRotatable(), EntityBlock {
return BlockEntityItemMonitor(blockPos, blockState)
}
override fun <T : BlockEntity?> getTicker(
p_153212_: Level,
p_153213_: BlockState,
p_153214_: BlockEntityType<T>
): BlockEntityTicker<T>? {
if (p_153214_ != Registry.BlockEntities.ITEM_MONITOR || p_153212_.isClientSide)
return null
return BlockEntityTicker { _, _, _, tile -> if (tile is BlockEntityItemMonitor) tile.tick() }
}
override fun getShape(
p_60555_: BlockState,
p_60556_: BlockGetter,

View File

@ -24,6 +24,7 @@ import ru.dbotthepony.mc.otm.ifHas
import ru.dbotthepony.mc.otm.menu.DriveRackMenu
import ru.dbotthepony.mc.otm.set
import ru.dbotthepony.mc.otm.graph.storage.StorageNetworkGraph
import ru.dbotthepony.mc.otm.storage.PoweredVirtualComponent
import ru.dbotthepony.mc.otm.storage.VirtualComponent
class BlockEntityDriveRack(p_155229_: BlockPos, p_155230_: BlockState) :
@ -35,11 +36,11 @@ class BlockEntityDriveRack(p_155229_: BlockPos, p_155230_: BlockState) :
super.setChanged(slot, new_state, old_state)
old_state.getCapability(MatteryCapability.DRIVE).ifPresent {
cell.computeIfAbsent(it.storageIdentity()) {c -> VirtualComponent(c)}.remove(it)
cell.computeIfAbsent(it.storageIdentity()) {c -> PoweredVirtualComponent(c, energy)}.remove(it)
}
new_state.getCapability(MatteryCapability.DRIVE).ifPresent {
cell.computeIfAbsent(it.storageIdentity()) {c -> VirtualComponent(c)}.add(it)
cell.computeIfAbsent(it.storageIdentity()) {c -> PoweredVirtualComponent(c, energy)}.add(it)
}
}
}
@ -70,6 +71,10 @@ class BlockEntityDriveRack(p_155229_: BlockPos, p_155230_: BlockState) :
energy = MatteryMachineEnergyStorage(this, MatteryMachineEnergyStorage.MachineType.WORKER, Fraction(80000))
}
fun tick() {
batteryChargeLoop()
}
override fun getDefaultDisplayName(): Component {
return NAME
}

View File

@ -27,7 +27,11 @@ class BlockEntityItemMonitor(p_155229_: BlockPos, p_155230_: BlockState) :
val cell = BasicStorageGraphNode()
init {
energy = MatteryMachineEnergyStorage(this, MatteryMachineEnergyStorage.MachineType.WORKER, Fraction(80000))
energy = MatteryMachineEnergyStorage(this, MatteryMachineEnergyStorage.MachineType.WORKER, Fraction(80_000))
}
fun tick() {
batteryChargeLoop()
}
override fun getDefaultDisplayName(): Component {

View File

@ -20,15 +20,11 @@ class ItemMatteryDrive : AbstractMatteryDrive<ItemStackWrapper>, IItemMatteryDri
constructor(capacity: Fraction, uuid: UUID) : super(capacity, uuid)
constructor(capacity: Fraction) : super(capacity)
override fun identity(): StorageObjectTuple<ItemStackWrapper> {
if (identity == null)
identity = StorageObjectRegistry.getOrError(ItemStackWrapper::class.java)
private val identity = StorageObjectRegistry.getOrError(ItemStackWrapper::class.java)
override fun identity() = identity
return identity!!
}
fun insertObject(item: ItemStack?, simulate: Boolean): ItemStack {
return insertStack(ItemStackWrapper(item!!), simulate).stack
fun insertObject(item: ItemStack, simulate: Boolean): ItemStack {
return insertStack(ItemStackWrapper(item), simulate).stack
}
override fun serializeStack(item: IStorageTuple<ItemStackWrapper>): CompoundTag? {
@ -90,8 +86,6 @@ class ItemMatteryDrive : AbstractMatteryDrive<ItemStackWrapper>, IItemMatteryDri
}
companion object {
private var identity: StorageObjectTuple<ItemStackWrapper>? = null
@JvmField
val DUMMY = ItemMatteryDrive(Fraction(0), UUID(0L, 0L), 0)
}

View File

@ -254,6 +254,24 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
@JvmOverloads constructor(value: BigDecimal, compact: Boolean = true) : this(powUnscaled(value.unscaledValue(), value.scale()), powScale(value.scale()), compact = compact)
@JvmOverloads constructor(value: BigDecimal, div: BigDecimal, compact: Boolean = true) : this(powUnscaled(value.unscaledValue(), value.scale()).multiply(powScale(div.scale())), powScale(value.scale()).multiply(powUnscaled(div.unscaledValue(), div.scale())), compact = compact)
override fun equals(other: Any?): Boolean {
if (other is Fraction) {
if (other.divisor == divisor)
return other.value == value
val a = value * other.divisor
val b = other.value * divisor
return a == b
}
return false
}
override fun hashCode(): Int {
return 31 * value.hashCode() + divisor.hashCode()
}
fun compactAndCanonize(): Fraction {
if (value == BigInteger.ZERO || value == BigInteger.ONE || divisor == BigInteger.ONE)
return this
@ -285,6 +303,11 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
return value == BigInteger.ZERO
}
fun isOne(): Boolean {
if (isNaN()) return false
return value != BigInteger.ZERO && value == divisor
}
fun compact(): Fraction {
if (isNaN()) return this
@ -455,6 +478,7 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
operator fun times(other: Fraction): Fraction {
if (isNaN()) return this
if (other.isNaN()) return other
if (other.isOne()) return this
if (compact)
return timesCompact(other)
@ -479,6 +503,7 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
operator fun div(other: Fraction): Fraction {
if (isNaN()) return this
if (other.isNaN()) return other
if (other.isOne()) return this
if (compact)
return divCompact(other)

View File

@ -1,11 +1,16 @@
package ru.dbotthepony.mc.otm.menu
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import ru.dbotthepony.mc.otm.Registry
import ru.dbotthepony.mc.otm.block.entity.BlockEntityItemMonitor
import ru.dbotthepony.mc.otm.capability.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.menu.data.INetworkedItemViewSupplier
import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView
import ru.dbotthepony.mc.otm.storage.ItemStackWrapper
import ru.dbotthepony.mc.otm.storage.PoweredVirtualComponent
import ru.dbotthepony.mc.otm.storage.VirtualComponent
class ItemMonitorMenu @JvmOverloads constructor(
p_38852_: Int,
@ -14,6 +19,8 @@ class ItemMonitorMenu @JvmOverloads constructor(
) : PoweredMatteryMenu(Registry.Menus.ITEM_MONITOR, p_38852_, inventory, tile), INetworkedItemViewSupplier {
@JvmField
val view = NetworkedItemView(inventory.player, this, tile == null)
private val subscribed: VirtualComponent<ItemStackWrapper>?
private val local: PoweredVirtualComponent<ItemStackWrapper>?
override fun getNetworkedItemView(): NetworkedItemView {
return view
@ -21,13 +28,24 @@ class ItemMonitorMenu @JvmOverloads constructor(
init {
if (tile != null) {
view.setComponent(tile.cell.getStorageGraph()!!.getVirtualComponent(ItemStackWrapper::class.java))
subscribed = tile.cell.getStorageGraph()!!.getVirtualComponent(ItemStackWrapper::class.java)
local = PoweredVirtualComponent(subscribed, tile.getCapability(MatteryCapability.ENERGY).resolve().get())
view.setComponent(local)
} else {
subscribed = null
local = null
}
addBatterySlot()
addInventorySlots()
}
override fun removed(p_38940_: Player) {
super.removed(p_38940_)
view.removed()
subscribed?.removeListenerAuto(local!!)
}
override fun broadcastChanges() {
super.broadcastChanges()
view.network()
@ -40,4 +58,4 @@ class ItemMonitorMenu @JvmOverloads constructor(
override fun getWorkingSlotEnd(): Int {
return 1
}
}
}

View File

@ -94,7 +94,7 @@ interface IStorageView<T : IStorageStack> : IStorageTrigger<T> {
* @param simulate whenever to simulate the action or not
* @return amount extracted
*/
fun extractStackCount(id: UUID, amount: Fraction, simulate: Boolean): Fraction? {
fun extractStackCount(id: UUID, amount: Fraction, simulate: Boolean): Fraction {
return extractStack(id, amount, simulate).count
}
@ -134,7 +134,7 @@ interface IStorageListener<T : IStorageStack> {
/**
* Fired on whenever an object is changes on listener we subscribed to
*/
fun changeObject(id: UUID, new_count: Fraction)
fun changeObject(id: UUID, newCount: Fraction)
/**
* Fired on whenever an object is removed from listener we subscribed to

View File

@ -0,0 +1,337 @@
package ru.dbotthepony.mc.otm.storage
import ru.dbotthepony.mc.otm.capability.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.core.Fraction
import java.util.*
import java.util.function.Supplier
class RemoteTuple<T : IStorageStack>(val obj: T, val remote_id: UUID, val provider: IStorageView<T>, val local: LocalTuple<T>) {
fun extract(amount: Fraction, simulate: Boolean): T {
return provider.extractStack(remote_id, amount, simulate)
}
fun extractCount(amount: Fraction, simulate: Boolean): Fraction {
return provider.extractStackCount(remote_id, amount, simulate)
}
override fun equals(other: Any?): Boolean {
return other is RemoteTuple<*> && other.remote_id == remote_id || other is UUID && other == remote_id
}
override fun hashCode(): Int {
return remote_id.hashCode()
}
}
class LocalTuple<T : IStorageStack>(override val stack: T, override val id: UUID, val tuples: ArrayList<RemoteTuple<T>>) : IStorageTuple<T> {
override fun id(): UUID {
return id
}
override fun stack(): T {
return stack
}
}
open class VirtualComponent<T : IStorageStack>(identity: Class<T>) : IStorageComponent<T>, IStorageListener<T> {
@JvmField
protected val identity: StorageObjectTuple<T>
final override fun storageIdentity() = identity.identity()
init {
this.identity = StorageObjectRegistry.getOrError(identity)
}
// удаленный UUID -> Кортеж
@JvmField
protected val remoteByUUID = HashMap<UUID, RemoteTuple<T>>()
// локальный UUID -> Локальный кортеж
@JvmField
protected val localByUUID = HashMap<UUID, LocalTuple<T>>()
// Хеш ключ -> Список Локальных кортежей
@JvmField
protected val partitions = HashMap<Any, ArrayList<LocalTuple<T>>>()
// ArrayList для скорости работы
@JvmField
protected val listeners = ArrayList<IStorageListener<T>>()
@JvmField
protected val consumers = ArrayList<IStorageConsumer<T>>()
open fun add(identity: IStorageIdentity<T>) {
if (identity is IStorageView<T>) {
identity.addListenerAuto(this)
} else if (identity is IStorageTrigger<T>) {
identity.addListener(this)
}
if (identity is IStorageConsumer<T> && !consumers.contains(identity)) {
consumers.add(identity);
}
}
open fun remove(identity: IStorageIdentity<T>) {
if (identity is IStorageView<T>) {
identity.removeListenerAuto(this)
} else if (identity is IStorageTrigger<T>) {
identity.removeListener(this)
}
if (identity is IStorageConsumer<T>) {
consumers.remove(identity)
}
}
override fun addListener(listener: IStorageListener<T>): Boolean {
if (!listeners.contains(listener)) {
listeners.add(listener)
return true
}
return false
}
override fun removeListener(listener: IStorageListener<T>): Boolean {
return listeners.remove(listener)
}
override fun getStack(id: UUID): T {
return localByUUID[id]?.stack ?: identity.empty
}
override fun getStacks(): List<IStorageTuple<T>> {
var capacity = 0
for (list in partitions.values) capacity += list.size
val output = ArrayList<IStorageTuple<T>>(capacity)
for (listing in partitions.values) output.addAll(listing)
return output
}
override fun addObject(stack: T, id: UUID, provider: IStorageView<T>) {
check(!remoteByUUID.containsKey(id)) { "Already tracking tuple with id $id" }
val items = partitions.computeIfAbsent(stack.partitionKey()) { ArrayList() }
var local: LocalTuple<T>? = null
for (item in items) {
if (item.stack.sameItem(stack)) {
item.stack.grow(stack.count)
local = item
break
}
}
val added = local == null
if (local == null) {
local = LocalTuple(stack.copy() as T, UUID.randomUUID(), ArrayList<RemoteTuple<T>>(1))
items.add(local)
localByUUID[local.id] = local
}
val remote = RemoteTuple(stack.copy() as T, id, provider, local)
local.tuples.add(remote)
remoteByUUID[id] = remote
if (added) {
for (listener in listeners) {
listener.addObject(local.stack, local.id, this)
}
} else {
for (listener in listeners) {
listener.changeObject(local.id, local.stack.count)
}
}
}
override fun changeObject(id: UUID, newCount: Fraction) {
assert(newCount > Fraction.ZERO)
val tuple = remoteByUUID[id] ?: throw IllegalStateException("No such tuple with id $id")
val diff = newCount - tuple.obj.count
tuple.obj.count = newCount
tuple.local.stack.grow(diff)
for (listener in listeners) {
listener.changeObject(tuple.local.id, tuple.local.stack.count)
}
}
override fun removeObject(id: UUID) {
val tuple = remoteByUUID[id] ?: throw IllegalStateException("No such tuple with id $id")
val key = tuple.local.stack.partitionKey()
tuple.local.stack.shrink(tuple.obj.count)
tuple.local.tuples.remove(tuple)
remoteByUUID.remove(id)
val a = tuple.local.stack.count <= Fraction.ZERO
val b = tuple.local.tuples.size == 0
if (a || b) {
check(a && b) { "View object is empty, but tuple list is not!" }
localByUUID.remove(tuple.local.id)
partitions[key]!!.remove(tuple.local)
for (listener in listeners) {
listener.removeObject(tuple.local.id)
}
}
}
override fun insertStack(stack: T, simulate: Boolean): T {
var leftover = stack
for (consumer in consumers) {
leftover = consumer.insertStack(leftover, simulate)
if (leftover.isEmpty()) {
return leftover
}
}
return leftover
}
override fun extractStack(id: UUID, amount: Fraction, simulate: Boolean): T {
if (amount.isZero())
return identity.empty
var amount = amount
val tuple: LocalTuple<T>? = localByUUID[id]
if (tuple == null || amount.isZero())
return identity.empty
if (amount <= Fraction.MINUS_ONE)
amount = tuple.stack.getMaxStackSize().orElse(tuple.stack.count)
val toExtract = tuple.stack.count.min(amount)
var extracted = Fraction.ZERO
val copy = tuple.stack.copy() as T
for (remote_tuple in tuple.tuples) {
extracted += remote_tuple.extractCount(toExtract - extracted, simulate)
if (extracted >= toExtract)
break
}
if (extracted > Fraction.ZERO) {
copy.count = extracted
return copy
}
return identity.empty()
}
}
open class PoweredVirtualComponent<T : IStorageStack>(identity: Class<T>, @JvmField val energyProvider: () -> IMatteryEnergyStorage) : VirtualComponent<T>(identity) {
constructor(identity: Class<T>, energyStorage: IMatteryEnergyStorage) : this(identity, {energyStorage})
constructor(other: VirtualComponent<T>, energyStorage: IMatteryEnergyStorage) : this(other.storageIdentity(), energyStorage) {
add(other)
}
override fun insertStack(stack: T, simulate: Boolean): T {
val required = stack.count * identity.energyPerOperation
val energy = energyProvider()
val extracted = energy.extractEnergyInner(required, true)
if (extracted.isZero()) {
return stack.copy() as T
}
if (extracted == required) {
val leftover = super.insertStack(stack, simulate)
if (leftover.isEmpty()) {
if (!simulate) {
energy.extractEnergyInner(required, false)
}
return leftover
}
if (!simulate) {
val requiredNew = (stack.count - leftover.count) * identity.energyPerOperation
energy.extractEnergyInner(requiredNew, false)
}
return leftover
}
val stack = stack.copy() as T
val oldCount = stack.count
stack.count = extracted / identity.energyPerOperation
val diff = oldCount - stack.count
val newRequired = stack.count * identity.energyPerOperation
val newExtracted = energy.extractEnergyInner(newRequired, true)
if (newExtracted == newRequired) {
val leftover = super.insertStack(stack, simulate)
if (leftover.isEmpty()) {
if (!simulate) {
energy.extractEnergyInner(newRequired, false)
}
leftover.count = diff
return leftover
}
if (!simulate) {
val requiredNew = (stack.count - leftover.count) * identity.energyPerOperation
energy.extractEnergyInner(requiredNew, false)
}
leftover.count += diff
return leftover
}
return stack
}
override fun extractStack(id: UUID, amount: Fraction, simulate: Boolean): T {
val required = amount * identity.energyPerOperation
val energy = energyProvider()
val extracted = energy.extractEnergyInner(required, true)
if (extracted.isZero()) {
return identity.empty
}
if (extracted == required) {
val extractedStack = super.extractStack(id, amount, simulate)
if (extractedStack.isEmpty()) {
return extractedStack
}
if (!simulate) {
if (extractedStack.count == amount) {
energy.extractEnergyInner(required, false)
} else {
energy.extractEnergyInner(extractedStack.count * identity.energyPerOperation, false)
}
}
return extractedStack
}
val amount = required / identity.energyPerOperation
val extractedStack = super.extractStack(id, amount, simulate)
if (extractedStack.isEmpty()) {
return extractedStack
}
if (!simulate) {
energy.extractEnergyInner(extractedStack.count * identity.energyPerOperation, false)
}
return extractedStack
}
}