Functioning again storage network

This commit is contained in:
DBotThePony 2022-01-01 00:46:27 +07:00
parent 695b540e3a
commit 64557a77f5
Signed by: DBot
GPG Key ID: DCC23B5715498507
15 changed files with 504 additions and 573 deletions

View File

@ -141,7 +141,7 @@ public class OverdriveThatMatters {
FMLJavaModLoadingContext.get().getModEventBus().register(Registry.AndroidResearch.class); FMLJavaModLoadingContext.get().getModEventBus().register(Registry.AndroidResearch.class);
FMLJavaModLoadingContext.get().getModEventBus().register(Registry.Stats.class); FMLJavaModLoadingContext.get().getModEventBus().register(Registry.Stats.class);
MinecraftForge.EVENT_BUS.register(DrivePool.class); MinecraftForge.EVENT_BUS.register(DrivePool.INSTANCE);
MinecraftForge.EVENT_BUS.register(ItemPortableCondensationDrive.class); MinecraftForge.EVENT_BUS.register(ItemPortableCondensationDrive.class);
FMLJavaModLoadingContext.get().getModEventBus().addListener(AndroidCapability::registerEffects); FMLJavaModLoadingContext.get().getModEventBus().addListener(AndroidCapability::registerEffects);

View File

@ -1,26 +0,0 @@
package ru.dbotthepony.mc.otm.capability.drive;
import net.minecraft.MethodsReturnNonnullByDefault;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import ru.dbotthepony.mc.otm.storage.IStorageTuple;
import ru.dbotthepony.mc.otm.storage.ItemStackWrapper;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.List;
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
public interface IItemMatteryDrive extends IMatteryDrive<ItemStackWrapper> {
/**
* @param item
* @return all items belonging to passed class
*/
List<IStorageTuple<ItemStackWrapper>> findItems(Item item);
/**
* @param stack
* @return all items that match this itemstack
*/
List<IStorageTuple<ItemStackWrapper>> findItems(ItemStack stack);
}

View File

@ -1,15 +0,0 @@
package ru.dbotthepony.mc.otm.capability.drive;
import net.minecraft.MethodsReturnNonnullByDefault;
import net.minecraft.world.item.ItemStack;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.UUID;
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
public interface IItemViewListener {
void addViewItem(ItemStack stack, UUID id_upstream);
void changeViewItem(UUID id_upstream, int new_count);
void removeViewItem(UUID id_upstream);
}

View File

@ -1,28 +0,0 @@
package ru.dbotthepony.mc.otm.capability.drive;
import net.minecraft.MethodsReturnNonnullByDefault;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import ru.dbotthepony.mc.otm.core.Fraction;
import ru.dbotthepony.mc.otm.storage.*;
import javax.annotation.ParametersAreNonnullByDefault;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
public interface IMatteryDrive<T extends IStorageStack> extends IStorageComponent<T> {
boolean isDirty();
void markDirty();
void markClean();
Fraction getStoredCount();
Fraction getCapacity();
// not extending INBTSerializable to avoid serializing it as forgecaps
CompoundTag serializeNBT();
void deserializeNBT(CompoundTag nbt);
}

View File

@ -101,12 +101,19 @@ public class ItemPortableCondensationDrive extends Item {
private class DriveCapability implements ICapabilityProvider { private class DriveCapability implements ICapabilityProvider {
private UUID uuid; private UUID uuid;
private final ItemStack stack; private final ItemStack stack;
private final LazyOptional<IMatteryDrive> resolver = LazyOptional.of(() -> { private final LazyOptional<IMatteryDrive> resolver = LazyOptional.of(() -> {
if (!DrivePool.isLegalAccess()) {
return ItemMatteryDrive.DUMMY;
}
final var uuid = this.uuid;
return DrivePool.get(uuid, (tag) -> { return DrivePool.get(uuid, (tag) -> {
var drive = new ItemMatteryDrive(capacity); var drive = new ItemMatteryDrive(capacity, uuid);
drive.deserializeNBT(tag); drive.deserializeNBT(tag);
return drive; return drive;
}, () -> new ItemMatteryDrive(capacity)); }, () -> new ItemMatteryDrive(capacity, uuid));
}); });
DriveCapability(ItemStack stack) { DriveCapability(ItemStack stack) {

View File

@ -28,6 +28,8 @@ operator fun CompoundTag.set(s: String, value: Short) = putShort(s, value)
operator fun CompoundTag.set(s: String, value: Long) = putLong(s, value) operator fun CompoundTag.set(s: String, value: Long) = putLong(s, value)
operator fun CompoundTag.set(s: String, value: String) = putString(s, value) operator fun CompoundTag.set(s: String, value: String) = putString(s, value)
operator fun CompoundTag.set(s: String, value: Boolean) = putBoolean(s, value) operator fun CompoundTag.set(s: String, value: Boolean) = putBoolean(s, value)
operator fun CompoundTag.set(s: String, value: ByteArray) = putByteArray(s, value)
operator fun CompoundTag.set(s: String, value: LongArray) = putLongArray(s, value)
inline fun CompoundTag.ifHas(s: String, consumer: (Tag) -> Unit) { inline fun CompoundTag.ifHas(s: String, consumer: (Tag) -> Unit) {
val tag = get(s) val tag = get(s)

View File

@ -0,0 +1,44 @@
package ru.dbotthepony.mc.otm.capability.drive
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.core.Fraction
import ru.dbotthepony.mc.otm.storage.IStorageComponent
import ru.dbotthepony.mc.otm.storage.IStorageStack
import ru.dbotthepony.mc.otm.storage.IStorageTuple
import ru.dbotthepony.mc.otm.storage.ItemStackWrapper
import java.util.*
interface IItemMatteryDrive : IMatteryDrive<ItemStackWrapper> {
/**
* @param item
* @return all items belonging to passed class
*/
fun findItems(item: Item): Collection<IStorageTuple<ItemStackWrapper>>
/**
* @param stack
* @return all items that match this itemstack
*/
fun findItems(stack: ItemStack): Collection<IStorageTuple<ItemStackWrapper>>
}
interface IItemViewListener {
fun addViewItem(stack: ItemStack, id_upstream: UUID)
fun changeViewItem(id_upstream: UUID, new_count: Int)
fun removeViewItem(id_upstream: UUID)
}
interface IMatteryDrive<T : IStorageStack> : IStorageComponent<T> {
val uuid: UUID
var isDirty: Boolean
val storedCount: Fraction
val driveCapacity: Fraction
// not extending INBTSerializable to avoid serializing it as forgecaps
fun serializeNBT(): CompoundTag
fun deserializeNBT(nbt: CompoundTag)
}

View File

@ -1,267 +1,233 @@
package ru.dbotthepony.mc.otm.capability.drive; package ru.dbotthepony.mc.otm.capability.drive
import net.minecraft.MethodsReturnNonnullByDefault; import ru.dbotthepony.mc.otm.core.Fraction.Companion.deserializeNBT
import net.minecraft.nbt.CompoundTag; import ru.dbotthepony.mc.otm.storage.IStorageStack
import net.minecraft.nbt.ListTag; import kotlin.jvm.JvmOverloads
import net.minecraft.nbt.Tag; import java.util.HashMap
import ru.dbotthepony.mc.otm.core.Fraction; import ru.dbotthepony.mc.otm.storage.IStorageTuple
import ru.dbotthepony.mc.otm.storage.*; import java.util.UUID
import ru.dbotthepony.mc.otm.storage.StorageObjectTuple
import ru.dbotthepony.mc.otm.storage.IStorageListener
import ru.dbotthepony.mc.otm.storage.StorageTuple
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.Tag
import ru.dbotthepony.mc.otm.core.Fraction
import ru.dbotthepony.mc.otm.ifHas
import ru.dbotthepony.mc.otm.set
import java.util.ArrayList
import java.util.HashSet
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.math.BigDecimal;
import java.util.*;
@MethodsReturnNonnullByDefault abstract class AbstractMatteryDrive<T : IStorageStack> @JvmOverloads constructor(
@ParametersAreNonnullByDefault override var driveCapacity: Fraction,
abstract public class AbstractMatteryDrive<T extends IStorageStack> implements IMatteryDrive<T> { override val uuid: UUID = UUID.randomUUID(),
protected final HashMap<Object, List<IStorageTuple<T>>> items = new HashMap<>(); var maxDifferentStacks: Int = 0xFFFF
protected final HashMap<UUID, IStorageTuple<T>> items_by_id = new HashMap<>(); ) : IMatteryDrive<T> {
abstract public StorageObjectTuple<T> identity(); @JvmField
protected val storedStacks = HashMap<Any, MutableList<IStorageTuple<T>>>()
protected boolean dirty = false; @JvmField
protected int different_stacks = 0; protected val storedByID = HashMap<UUID, IStorageTuple<T>>()
protected Fraction stored = Fraction.ZERO; abstract fun identity(): StorageObjectTuple<T>
protected int max_different_stacks; override var isDirty = false
protected Fraction capacity; set(value) {
if (value != field && value && DrivePool.isLegalAccess()) {
public AbstractMatteryDrive(Fraction capacity, int max_different_stacks) { DrivePool.markDirty(uuid)
this.capacity = capacity;
this.max_different_stacks = max_different_stacks;
} }
public AbstractMatteryDrive(Fraction capacity) { field = value
this(capacity, 0xFFFF);
} }
@Override var storedDifferentStacks = 0
public boolean isDirty() {
return dirty;
}
@Override override var storedCount = Fraction.ZERO
public void markDirty() { protected set
dirty = true;
}
@Override override fun insertStack(stack: T, simulate: Boolean): T {
public void markClean() { val maxInsert = driveCapacity.minus(storedCount).min(stack.count)
dirty = false; if (maxInsert <= Fraction.ZERO) return stack
}
@Override val listing = storedStacks.computeIfAbsent(stack.partitionKey()) { ArrayList() }
public Fraction getStoredCount() {
return stored;
}
@Override for (state in listing) {
public Fraction getCapacity() {
return capacity;
}
@Nonnull
@Override
@SuppressWarnings("unchecked")
public T insertStack(T stack, boolean simulate) {
var max_insert = getCapacity().minus(getStoredCount()).min(stack.getCount());
if (max_insert.compareTo(Fraction.ZERO) <= 0)
return stack;
final var listing = items.computeIfAbsent(stack.partitionKey(), (key) -> new ArrayList<>());
for (var state : listing) {
if (state.stack().sameItem(stack)) { if (state.stack().sameItem(stack)) {
if (!simulate) { if (!simulate) {
state.stack().grow(max_insert); state.stack().grow(maxInsert)
stored = stored.plus(max_insert); storedCount += maxInsert
for (var listener : listeners2) { for (listener in listeners) {
listener.changeObject(state.id(), state.stack().getCount()); listener.changeObject(state.id(), state.stack().count)
} }
markDirty(); isDirty = true
} }
final var copy_item = (T) stack.copy(); val copy = stack.copy() as T
copy_item.shrink(max_insert); copy.shrink(maxInsert)
return copy_item; return copy
} }
} }
if (different_stacks >= max_different_stacks) { if (storedDifferentStacks >= maxDifferentStacks) {
return stack; return stack
} }
if (!simulate) { if (!simulate) {
different_stacks++; storedDifferentStacks++
stored = stored.plus(max_insert); storedCount = storedCount.plus(maxInsert)
final var copy = (T) stack.copy(); val copy = stack.copy() as T
copy.setCount(max_insert); copy.count = maxInsert
final var state = new StorageTuple<>(UUID.randomUUID(), copy);
listing.add(state);
items_by_id.put(state.id(), state);
for (var listener : listeners2) { val state = StorageTuple(UUID.randomUUID(), copy)
listener.addObject(state.stack(), state.id(), this); listing.add(state)
storedByID[state.id()] = state
for (listener in listeners) {
listener.addObject(state.stack(), state.id(), this)
} }
markDirty(); isDirty = true
} }
final var copy_item = (T) stack.copy(); val copy = stack.copy() as T
copy_item.shrink(max_insert); copy.shrink(maxInsert)
return copy_item; return copy
} }
@Nonnull override fun extractStack(id: UUID, amount: Fraction, simulate: Boolean): T {
@Override var amount = amount
@SuppressWarnings("unchecked") val get = storedByID[id] ?: return identity().empty()
public T extractStack(UUID id, Fraction amount, boolean simulate) {
var get = items_by_id.get(id);
if (get == null) if (amount <= Fraction.ZERO)
return identity().empty(); amount = get.stack.getMaxStackSize().orElse(get.stack.count)
if (amount.compareTo(Fraction.ZERO) <= 0) amount = amount.min(get.stack.count)
amount = get.stack().getMaxStackSize().orElse(get.stack().getCount());
amount = amount.min(get.stack().getCount()); if (amount <= Fraction.ZERO)
return identity().empty()
if (amount.compareTo(Fraction.ZERO) <= 0) val copy = get.stack.copy() as T
return identity().empty(); copy.count = amount
final var copy = (T) get.stack().copy();
copy.setCount(amount);
if (!simulate) { if (!simulate) {
if (amount.compareTo(get.stack().getCount()) == 0) { if (amount.compareTo(get.stack.count) == 0) {
var listing = items.get(get.stack().partitionKey()); val listing = storedStacks[get.stack.partitionKey()]!!
listing.remove(get); listing.remove(get)
different_stacks--; storedDifferentStacks--
for (var listener : listeners2) { for (listener in listeners) {
listener.removeObject(get.id()); listener.removeObject(get.id())
} }
if (listing.size() == 0) { if (listing.size == 0) {
items.remove(get.stack().partitionKey()); storedStacks.remove(get.stack.partitionKey())
} }
} }
stored = stored.minus(amount); storedCount = storedCount.minus(amount)
get.stack().shrink(amount); get.stack.shrink(amount)
if (get.stack().getCount().compareTo(Fraction.ZERO) != 0) { if (get.stack.count.compareTo(Fraction.ZERO) != 0) {
for (var listener : listeners2) { for (listener in listeners) {
listener.changeObject(get.id(), get.stack().getCount()); listener.changeObject(get.id(), get.stack.count)
} }
} }
markDirty(); isDirty = true
} }
return copy; return copy
} }
@Nullable protected abstract fun serializeStack(item: IStorageTuple<T>): CompoundTag?
abstract protected CompoundTag serializeStack(IStorageTuple<T> item); protected abstract fun deserializeStack(tag: CompoundTag): T?
@Override override fun serializeNBT(): CompoundTag {
public CompoundTag serializeNBT() { val compound = CompoundTag()
final var compound = new CompoundTag();
compound.putString("capacity", capacity.toString()); compound["capacity"] = driveCapacity.serializeNBT()
compound.putInt("max_different_stacks", max_different_stacks); compound["max_different_stacks"] = maxDifferentStacks
var list_items = new ListTag(); val list = ListTag()
compound.put("items", list_items); compound["items"] = list
for (var entry : items.entrySet()) { for (value in storedStacks.values) {
for (var stack : entry.getValue()) { for (stack in value) {
CompoundTag entry_compound; val serialized = serializeStack(stack)
if ((entry_compound = serializeStack(stack)) != null) { if (serialized != null) {
entry_compound.putLongArray("id", new long[] { stack.id().getMostSignificantBits(), stack.id().getLeastSignificantBits() }); serialized["id"] = longArrayOf(stack.id().mostSignificantBits, stack.id().leastSignificantBits)
list_items.add(entry_compound); list.add(serialized)
} }
} }
} }
return compound
return compound;
} }
@Nullable override fun deserializeNBT(nbt: CompoundTag) {
abstract protected T deserializeStack(CompoundTag tag); storedStacks.clear()
storedByID.clear()
storedCount = Fraction.ZERO
storedDifferentStacks = 0
@Override nbt.ifHas("capacity") {
public void deserializeNBT(CompoundTag nbt) { driveCapacity = deserializeNBT(it)
items.clear(); }
items_by_id.clear();
stored = Fraction.ZERO;
different_stacks = 0;
if (nbt.contains("capacity")) maxDifferentStacks = nbt.getInt("max_different_stacks")
capacity = Fraction.deserializeNBT(nbt.get("capacity"));
max_different_stacks = nbt.getInt("max_different_stacks"); for (entry in nbt.getList("items", Tag.TAG_COMPOUND.toInt())) {
if (entry is CompoundTag) {
for (var _entry : nbt.getList("items", Tag.TAG_COMPOUND)) { val stack = deserializeStack(entry)
if (_entry instanceof CompoundTag entry) {
final var stack = deserializeStack(entry);
if (stack != null) { if (stack != null) {
stored = stored.plus(stack.getCount()); storedCount = storedCount.plus(stack.count)
different_stacks++; storedDifferentStacks++
final var id = entry.getLongArray("id");
final var tuple = new StorageTuple<>(id.length == 2 ? new UUID(id[0], id[1]) : UUID.randomUUID(), stack); val id = entry.getLongArray("id")
items.computeIfAbsent(stack.partitionKey(), (key) -> new ArrayList<>()).add(tuple); val tuple = StorageTuple(if (id.size == 2) UUID(id[0], id[1]) else UUID.randomUUID(), stack)
items_by_id.put(tuple.id(), tuple);
storedStacks.computeIfAbsent(stack.partitionKey()) { ArrayList() }.add(tuple)
storedByID[tuple.id] = tuple
} }
} }
} }
} }
@Override override fun storageIdentity(): Class<T> {
public Class<T> storageIdentity() { return identity().identity()
return identity().identity();
} }
@Override override fun getStack(id: UUID): T {
public T getStack(UUID id) { return storedByID[id]?.stack ?: identity().empty()
var get = items_by_id.get(id);
return get == null ? identity().empty() : get.stack();
} }
@Override override fun getStacks(): List<IStorageTuple<T>> {
public List<IStorageTuple<T>> getStacks() { var amount = 0
int amount = 0;
for (var listing : items.values()) for (listing in storedStacks.values)
amount += listing.size(); amount += listing.size
var list = new ArrayList<IStorageTuple<T>>(amount); val list = ArrayList<IStorageTuple<T>>(amount)
for (var listing : items.values()) { for (listing in storedStacks.values) {
list.addAll(listing); list.addAll(listing)
} }
return list; return list
} }
protected final HashSet<IStorageListener<T>> listeners2 = new HashSet<>(); @JvmField
protected val listeners = HashSet<IStorageListener<T>>()
@Override override fun addListener(listener: IStorageListener<T>): Boolean {
public boolean addListener(IStorageListener<T> listener) { return listeners.add(listener)
return listeners2.add(listener);
} }
@Override override fun removeListener(listener: IStorageListener<T>): Boolean {
public boolean removeListener(IStorageListener<T> listener) { return listeners.remove(listener)
return listeners2.remove(listener);
} }
} }

View File

@ -1,30 +1,24 @@
package ru.dbotthepony.mc.otm.capability.drive; package ru.dbotthepony.mc.otm.capability.drive
import net.minecraft.CrashReport;
import net.minecraft.MethodsReturnNonnullByDefault;
import net.minecraft.ReportedException;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.server.MinecraftServer;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.event.server.ServerStartingEvent;
import net.minecraftforge.event.server.ServerStoppingEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.HashMap
import java.util.UUID
import net.minecraft.ReportedException
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtIo
import net.minecraft.CrashReport
import net.minecraft.server.MinecraftServer
import java.util.concurrent.locks.LockSupport
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.eventbus.api.EventPriority
import net.minecraftforge.event.TickEvent.ServerTickEvent
import net.minecraftforge.event.TickEvent
import net.minecraftforge.event.server.ServerAboutToStartEvent
import net.minecraftforge.event.server.ServerStoppingEvent
import net.minecraftforge.event.world.WorldEvent
import org.apache.logging.log4j.LogManager
import java.io.File
import java.lang.ref.WeakReference
import java.util.ArrayList
/** /**
* Let me answer everyone's questions about: * Let me answer everyone's questions about:
@ -36,234 +30,278 @@ import java.util.function.Supplier;
* 1. This data can not be stored inside ItemStack.ForgeCaps due to it's size * 1. This data can not be stored inside ItemStack.ForgeCaps due to it's size
* 2. This data can not be stored inside unshared (server only) nbt tag because, again, mods prone to use and interact with * 2. This data can not be stored inside unshared (server only) nbt tag because, again, mods prone to use and interact with
* it wrong, causing loss of stored data or mods exposing full content of a drive inside their own tag (which cause very real "NBT size too large" * it wrong, causing loss of stored data or mods exposing full content of a drive inside their own tag (which cause very real "NBT size too large"
* network kicks, often locking players out of server * network kicks, often locking players out of server/singleplayer worlds
* 3. net.minecraft.world.level.saveddata.SaveData is for storing everything inside one dat file, which * 3. net.minecraft.world.level.saveddata.SaveData is for storing everything inside one dat file, which
* is performance tanking, because we have to write *entire* NBT on each save, not the data of Drive which got changed * is performance tanking, because we have to write *entire* NBT on each save, not the data of Drives that are dirty
*/ */
@MethodsReturnNonnullByDefault @Suppress("unused")
@ParametersAreNonnullByDefault object DrivePool {
public class DrivePool { private val LOGGER = LogManager.getLogger()
private static final Logger LOGGER = LogManager.getLogger(); private val pool = HashMap<UUID, WeakDriveReference>()
private var thread: Thread? = null
private var stopping = false
private var exception: ReportedException? = null
private const val DATA_PATH = "data/otm_drives"
public static class Entry { @JvmStatic
public final UUID id; operator fun <T : IMatteryDrive<*>> get(
public final IMatteryDrive<?> drive; id: UUID,
public long last_access = System.currentTimeMillis(); factory_stored: (CompoundTag) -> T,
factory_empty: () -> T
): T {
if (!isLegalAccess())
throw IllegalAccessException("Can not access drive pool from outside of server thread.")
Entry(UUID id, IMatteryDrive<?> drive) { val get = pool[id]
this.id = id;
this.drive = drive;
}
@Nullable
public CompoundTag sync() {
if (!drive.isDirty()) {
return null;
}
var tag = drive.serializeNBT();
drive.markClean();
last_access = System.currentTimeMillis();
return tag;
}
public IMatteryDrive<?> access() {
last_access = System.currentTimeMillis();
return drive;
}
}
private static HashMap<UUID, Entry> pool = new HashMap<>();
private static Thread thread;
private static boolean stopping = false;
private static ReportedException exception;
public static final String DATA_PATH = "data/otm_drives";
public static <T extends IMatteryDrive<?>> T get(UUID id, Function<CompoundTag, T> factory_stored, Supplier<T> factory_empty) {
var get = pool.get(id);
if (get != null) { if (get != null) {
return (T) get.access(); val accessed = get.access()
if (accessed != null) {
return accessed as T
}
} }
var file = new File(base_directory, id.toString() + ".dat"); val file = File(baseDirectory, "$id.dat")
if (file.exists()) { if (file.exists()) {
try { try {
var factorize = factory_stored.apply(NbtIo.readCompressed(file)); val factorize = factory_stored(NbtIo.readCompressed(file))
pool.put(id, new Entry(id, factorize)); pool[id] = WeakDriveReference(factorize)
return factorize; return factorize
} catch(Throwable error) { } catch (error: Throwable) {
var report = new CrashReport("Reading OTM Drive from disk", error); val report = CrashReport("Reading OTM Drive from disk", error)
var category = report.addCategory("Corrupt drive"); val category = report.addCategory("Corrupt drive")
category.setDetail("UUID", id); category.setDetail("UUID", id)
throw new ReportedException(report); throw ReportedException(report)
} }
} }
var factorize = factory_empty.get(); val factorize = factory_empty()
pool.put(id, new Entry(id, factorize)); pool[id] = WeakDriveReference(factorize)
return factorize
return factorize;
} }
public static void put(UUID id, IMatteryDrive<?> drive) { @JvmStatic
pool.put(id, new Entry(id, drive)); fun put(id: UUID, drive: IMatteryDrive<*>) {
if (!isLegalAccess())
throw IllegalAccessException("Can not access drive pool from outside of server thread.")
pool[id] = WeakDriveReference(drive)
} }
private record BacklogLine(UUID file, CompoundTag tag, IMatteryDrive<?> drive) { @JvmStatic
@Override fun markDirty(id: UUID) {
public boolean equals(Object obj) { if (!isLegalAccess())
return obj instanceof BacklogLine line && line.file.equals(file) || obj instanceof UUID uuid && uuid.equals(file); throw IllegalAccessException("Can not access drive pool from outside of server thread.")
}
pool[id]?.access()
} }
private static ArrayList<BacklogLine> backlog = new ArrayList<>(); private var backlog = ArrayList<BacklogLine>()
private static File base_directory; private var baseDirectory: File? = null
private var serverThread: Thread? = null
private static void thread(MinecraftServer server) { private fun thread(server: MinecraftServer) {
while (true) { while (true) {
LockSupport.park(); LockSupport.park()
if (stopping) { if (stopping) {
return; return
} }
try { try {
// TODO: Syncing status with main thread, since server stop can occur during work. // TODO: Syncing status with main thread, since server stop can occur during work.
sync(); sync()
} catch (ReportedException error) { } catch (error: ReportedException) {
exception = error; exception = error
return; return
} }
} }
} }
@SubscribeEvent(priority = EventPriority.LOWEST) @SubscribeEvent(priority = EventPriority.LOWEST)
public static void onServerPostTick(TickEvent.ServerTickEvent event) { fun onServerPostTick(event: ServerTickEvent) {
if (event.phase == TickEvent.Phase.END) { if (event.phase == TickEvent.Phase.END) {
if (exception != null) { if (exception != null) {
throw exception; throw exception!!
} }
} }
} }
/**
* Returns whenever running on server thread. Calling [get], [put] or [markDirty] will throw an exception if this returns false.
*
* If you are making your own drive for your own storage stack, feed code outside of server thread (e.g. client code) dummy disk.
*/
@JvmStatic
fun isLegalAccess(): Boolean {
return serverThread == null || Thread.currentThread() === serverThread
}
@SubscribeEvent(priority = EventPriority.HIGHEST) @SubscribeEvent(priority = EventPriority.HIGHEST)
public static void serverStartEvent(ServerStartingEvent event) { fun serverStartEvent(event: ServerAboutToStartEvent) {
if (thread != null && thread.isAlive()) { if (thread != null && thread!!.isAlive) {
LOGGER.error("FMLServerStartedEvent fired twice."); LOGGER.error("FMLServerStartedEvent fired twice.")
LOGGER.error("Attempting to start another DrivePool I/O thread when already running one!"); LOGGER.error("Attempting to start another DrivePool I/O thread when already running one!")
return; return
} }
pool.clear(); pool.clear()
final var server = event.getServer(); val server = event.server
base_directory = new File(server.storageSource.getWorldDir().toFile(), DATA_PATH); baseDirectory = File(server.storageSource.worldDir.toFile(), DATA_PATH)
base_directory.mkdirs(); baseDirectory!!.mkdirs()
stopping = false; serverThread = Thread.currentThread()
thread = new Thread(null, () -> DrivePool.thread(server), "Overdrive That Matters DrivePool IO");
thread.start(); stopping = false
thread = Thread(null, { thread(server) }, "Overdrive That Matters DrivePool IO")
thread!!.start()
} }
@SubscribeEvent(priority = EventPriority.HIGHEST) @SubscribeEvent(priority = EventPriority.HIGHEST)
public static void serverStopEvent(ServerStoppingEvent event) { fun serverStopEvent(event: ServerStoppingEvent) {
if (thread != null && thread.isAlive()) { val thread = thread
stopping = true;
LockSupport.unpark(thread); if (thread != null && thread.isAlive) {
stopping = true
LockSupport.unpark(thread)
} }
if (exception == null) { if (exception == null) {
writeBacklog(); writeBacklog()
sync(); sync()
} }
pool.clear(); pool.clear()
} }
@SubscribeEvent @SubscribeEvent
public static void onWorldSave(WorldEvent.Save event) { fun onWorldSave(event: WorldEvent.Save) {
writeBacklog(); writeBacklog()
} }
public static void writeBacklog() { private fun writeBacklog() {
var sync_required = false; var needsSync = false
ArrayList<UUID> remove_keys = null; var removeKeys: ArrayList<UUID>? = null
for (var entry : pool.entrySet()) { for ((key, value) in pool) {
var tag = entry.getValue().sync(); val tag = value.sync()
if (tag != null) { if (tag != null) {
LOGGER.info("Serializing OTM Drive {}", entry.getKey()); LOGGER.info("Serializing OTM Drive {}", key)
val index = backlog.indexOf<Any>(key)
var index = backlog.indexOf(entry.getKey());
if (index != -1) { if (index != -1) {
backlog.set(index, new BacklogLine(entry.getKey(), tag, entry.getValue().drive)); backlog[index] = BacklogLine(key, tag, value.drive()!!)
} else { } else {
backlog.add(new BacklogLine(entry.getKey(), tag, entry.getValue().drive)); backlog.add(BacklogLine(key, tag, value.drive()!!))
} }
sync_required = true; needsSync = true
} else if (entry.getValue().last_access < System.currentTimeMillis() - 600_000) { } else if (!value.valid()) {
// no access or changes in 600 seconds - unload if (removeKeys == null)
if (remove_keys == null) removeKeys = ArrayList()
remove_keys = new ArrayList<>();
remove_keys.add(entry.getKey()); removeKeys.add(key)
} }
} }
if (remove_keys != null) { if (removeKeys != null) {
for (var key : remove_keys) { for (key in removeKeys) {
pool.remove(key); pool.remove(key)
} }
} }
if (sync_required) { if (needsSync) {
LockSupport.unpark(thread); LockSupport.unpark(thread)
} }
} }
public static void sync() { private fun sync() {
var copy = backlog; val copy = backlog
backlog = new ArrayList<>(); backlog = ArrayList()
for (var entry : copy) { for (entry in copy) {
try { try {
LOGGER.info("Syncing OTM Drive {}", entry.file); LOGGER.info("Syncing OTM Drive {}", entry.file)
var old_file = new File(base_directory, entry.file + ".dat_old");
if (old_file.exists()) { val oldFile = File(baseDirectory, entry.file.toString() + ".dat_old")
if (!old_file.delete()) {
throw new IllegalStateException("Unable to delete old dat file"); if (oldFile.exists()) {
} check(oldFile.delete()) { "Unable to delete old dat file" }
} }
var file = new File(base_directory, entry.file + ".dat"); val newFile = File(baseDirectory, entry.file.toString() + ".dat")
if (file.exists()) { if (newFile.exists()) {
if (!file.renameTo(old_file)) { check(newFile.renameTo(oldFile)) { "Unable to move old dat file" }
throw new IllegalStateException("Unable to move old dat file");
}
} }
NbtIo.writeCompressed(entry.tag, file); NbtIo.writeCompressed(entry.tag, newFile)
} catch(Throwable error) { } catch (error: Throwable) {
var report = new CrashReport("Syncing OTM Drives state to disk", error); val report = CrashReport("Syncing OTM Drives state to disk", error)
var category = report.addCategory("Corrupt drive"); val category = report.addCategory("Corrupt drive")
category.setDetail("UUID", entry.file.toString()); category.setDetail("UUID", entry.file.toString())
category.setDetail("Stored item count", entry.drive.getStoredCount()); category.setDetail("Stored item count", entry.drive.storedCount)
category.setDetail("Capacity", entry.drive.getCapacity()); category.setDetail("Capacity", entry.drive.driveCapacity)
throw ReportedException(report)
if (entry.drive instanceof AbstractMatteryDrive drive) {
category.setDetail("Amount of different stacks", drive.different_stacks);
category.setDetail("Max amount of different stacks", drive.max_different_stacks);
}
throw new ReportedException(report);
} }
} }
} }
} }
private class WeakDriveReference(drive: IMatteryDrive<*>) {
private var drive: IMatteryDrive<*>? = drive
private val weak = WeakReference(drive)
fun valid(): Boolean {
return drive != null || weak.get() != null
}
fun drive(): IMatteryDrive<*>? {
return drive ?: weak.get()
}
fun sync(): CompoundTag? {
var drive = drive
if (drive == null) {
drive = weak.get()
if (drive == null)
return null
}
if (!drive.isDirty) {
this.drive = null
return null
}
val tag = drive.serializeNBT()
drive.isDirty = false
this.drive = null
return tag
}
fun access(): IMatteryDrive<*>? {
this.drive = weak.get()
return drive
}
}
private data class BacklogLine(val file: UUID, val tag: CompoundTag, val drive: IMatteryDrive<*>) {
override fun equals(other: Any?): Boolean {
if (other is BacklogLine) {
return other.file == file
}
if (other is UUID) {
return other == file
}
return false
}
}

View File

@ -1,124 +1,98 @@
package ru.dbotthepony.mc.otm.capability.drive; package ru.dbotthepony.mc.otm.capability.drive
import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceLocation
import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item
import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items
import net.minecraft.world.item.Items; import net.minecraftforge.registries.RegistryManager
import net.minecraftforge.registries.RegistryManager; import ru.dbotthepony.mc.otm.core.Fraction
import ru.dbotthepony.mc.otm.core.Fraction; import ru.dbotthepony.mc.otm.set
import ru.dbotthepony.mc.otm.storage.IStorageTuple; import ru.dbotthepony.mc.otm.storage.IStorageTuple
import ru.dbotthepony.mc.otm.storage.ItemStackWrapper; import ru.dbotthepony.mc.otm.storage.ItemStackWrapper
import ru.dbotthepony.mc.otm.storage.StorageObjectRegistry; import ru.dbotthepony.mc.otm.storage.StorageObjectRegistry
import ru.dbotthepony.mc.otm.storage.StorageObjectTuple; import ru.dbotthepony.mc.otm.storage.StorageObjectTuple
import java.util.*
import javax.annotation.Nullable; class ItemMatteryDrive : AbstractMatteryDrive<ItemStackWrapper>, IItemMatteryDrive {
import javax.annotation.ParametersAreNonnullByDefault; constructor(capacity: Fraction, max_different_stacks: Int) : super(capacity, maxDifferentStacks = max_different_stacks)
import java.math.BigDecimal; constructor(capacity: Fraction, uuid: UUID, max_different_stacks: Int) : super(capacity, uuid, max_different_stacks)
import java.util.ArrayList; constructor(capacity: Fraction, uuid: UUID) : super(capacity, uuid)
import java.util.List; constructor(capacity: Fraction) : super(capacity)
import java.util.Objects;
@MethodsReturnNonnullByDefault override fun identity(): StorageObjectTuple<ItemStackWrapper> {
@ParametersAreNonnullByDefault
public class ItemMatteryDrive extends AbstractMatteryDrive<ItemStackWrapper> implements IItemMatteryDrive {
private static StorageObjectTuple<ItemStackWrapper> identity;
public ItemMatteryDrive(Fraction capacity, int max_different_stacks) {
super(capacity, max_different_stacks);
}
public ItemMatteryDrive(Fraction capacity) {
super(capacity);
}
@Override
public StorageObjectTuple<ItemStackWrapper> identity() {
if (identity == null) if (identity == null)
identity = StorageObjectRegistry.getOrError(ItemStackWrapper.class); identity = StorageObjectRegistry.getOrError(ItemStackWrapper::class.java)
return identity; return identity!!
} }
public ItemStack insertObject(ItemStack item, boolean simulate) { fun insertObject(item: ItemStack?, simulate: Boolean): ItemStack {
return insertStack(new ItemStackWrapper(item), simulate).stack(); return insertStack(ItemStackWrapper(item!!), simulate).stack
} }
@Override override fun serializeStack(item: IStorageTuple<ItemStackWrapper>): CompoundTag? {
protected CompoundTag serializeStack(IStorageTuple<ItemStackWrapper> item) { val tag = CompoundTag()
final var tag = new CompoundTag(); val location = item.stack().stack.item.registryName!!.toString()
final var location = Objects.requireNonNull(item.stack().stack().getItem().getRegistryName(), "Missing registry name for stored Item").toString();
tag.putString("item", location); tag["item"] = location
tag.putInt("count", item.stack().stack().getCount()); tag["count"] = item.stack.stack.count
CompoundTag item_tag; val itag = item.stack.stack.tag
if ((item_tag = item.stack().stack().getTag()) != null) { if (itag != null) {
tag.put("data", item_tag); tag["data"] = itag
} }
return tag; return tag
} }
@Nullable override fun deserializeStack(tag: CompoundTag): ItemStackWrapper? {
@Override val item = RegistryManager.ACTIVE.getRegistry(Item::class.java).getValue(ResourceLocation(tag.getString("item")))
protected ItemStackWrapper deserializeStack(CompoundTag tag) {
final var item = RegistryManager.ACTIVE.getRegistry(Item.class).getValue(new ResourceLocation(tag.getString("item")));
if (item != null && item != Items.AIR) { if (item != null && item !== Items.AIR) {
final var count = tag.getInt("count"); val count = tag.getInt("count")
final var data = tag.get("data"); val itemstack = ItemStack(item, count)
itemstack.tag = tag["data"] as? CompoundTag?
final var itemstack = new ItemStack(item, count); return ItemStackWrapper(itemstack)
if (data != null) {
itemstack.setTag((CompoundTag) data);
} }
return new ItemStackWrapper(itemstack); return null
} }
return null; override fun findItems(item: Item): List<IStorageTuple<ItemStackWrapper>> {
val list = storedStacks[item]
return if (list != null) java.util.List.copyOf(list) else emptyList()
} }
override fun findItems(stack: ItemStack): List<IStorageTuple<ItemStackWrapper>> {
val list = storedStacks[stack.item] ?: return emptyList()
@Override var amount = 0
public List<IStorageTuple<ItemStackWrapper>> findItems(Item item) {
var list = items.get(item);
if (list != null) for (_stack in list) {
return List.copyOf(list); if (ItemStack.tagMatches(_stack.stack.stack, stack)) {
amount++
return List.of();
}
@Override
public List<IStorageTuple<ItemStackWrapper>> findItems(ItemStack stack) {
var list = items.get(stack.getItem());
if (list == null)
return List.of();
var amount = 0;
for (var _stack : list) {
if (ItemStack.tagMatches(_stack.stack().stack(), stack)) {
amount++;
} }
} }
var build_list = new ArrayList<IStorageTuple<ItemStackWrapper>>(amount); val build_list = ArrayList<IStorageTuple<ItemStackWrapper>>(amount)
var i = 0; var i = 0
for (var _stack : list) { for (_stack in list) {
if (ItemStack.tagMatches(_stack.stack().stack(), stack)) { if (ItemStack.tagMatches(_stack.stack.stack, stack)) {
build_list.set(i, _stack); build_list[i] = _stack
i++; i++
} }
} }
return build_list; return build_list
}
companion object {
private var identity: StorageObjectTuple<ItemStackWrapper>? = null
@JvmField
val DUMMY = ItemMatteryDrive(Fraction(0), UUID(0L, 0L), 0)
} }
} }

View File

@ -52,17 +52,15 @@ open class BasicStorageGraphNode : IStorageGraphNode {
} }
} }
detachStorage()
components.add(component) components.add(component)
attachStorage() getStorageGraph()?.add(component)
} }
fun removeStorageComponent(component: IStorageIdentity<*>) { fun removeStorageComponent(component: IStorageIdentity<*>) {
for (component1 in components) { for (component1 in components) {
if (component === component1 || component1.storageIdentity() == component.storageIdentity()) { if (component === component1 || component1.storageIdentity() == component.storageIdentity()) {
detachStorage()
components.remove(component1) components.remove(component1)
attachStorage() getStorageGraph()?.remove(component)
return return
} }
} }
@ -85,6 +83,4 @@ open class BasicStorageGraphNode : IStorageGraphNode {
fun get(): LazyOptional<IStorageGraphNode> { fun get(): LazyOptional<IStorageGraphNode> {
return if (valid) resolver else LazyOptional.empty() return if (valid) resolver else LazyOptional.empty()
} }
override var storageGrid: StorageNetworkGraph? = null
} }

View File

@ -11,23 +11,12 @@ interface IStorageGraphNode {
/** /**
* Return all providers that this cell can store. Or nothing, if it is not a storage. * Return all providers that this cell can store. Or nothing, if it is not a storage.
* *
* If you don't know what you are going to store, return empty list, then, when you make up your * If you don't know what you are going to store, you may return nothing.
* mind by either player tweaking settings or something,
* call helper method detachStorage();
* add whatever going to end up in getComponents();
* add helper method attachStorage();
* *
* Failing to do it in this order will cause IllegalStateException * If something pops up, call [getStorageGraph], and then, call [StorageNetworkGraph.add].
*
* @return a list of all providers, inheriting IStorageIdentity, or an empty list
*/ */
fun getComponents(): Collection<IStorageIdentity<*>> fun getComponents(): Collection<IStorageIdentity<*>>
fun getComponentsForView(): Collection<IStorageIdentity<*>> = getComponents()
fun getComponentsForInterfaces(): Collection<IStorageIdentity<*>> = getComponents()
fun getComponentsForExporters(): Collection<IStorageIdentity<*>> = getComponents()
fun detachStorage() = storageGrid?.detach(getAsStorageNode())
fun attachStorage() = storageGrid?.attach(getAsStorageNode())
var storageGrid: StorageNetworkGraph?
fun getAsStorageNode(): Graph6Node<IStorageGraphNode> fun getAsStorageNode(): Graph6Node<IStorageGraphNode>
fun getStorageGraph(): StorageNetworkGraph? = getAsStorageNode().graph as StorageNetworkGraph?
} }

View File

@ -1,6 +1,5 @@
package ru.dbotthepony.mc.otm.graph.storage package ru.dbotthepony.mc.otm.graph.storage
import net.minecraft.MethodsReturnNonnullByDefault
import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.entity.BlockEntity
import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.MatteryCapability
@ -9,10 +8,7 @@ import ru.dbotthepony.mc.otm.graph.Graph6Node
import ru.dbotthepony.mc.otm.storage.IStorageIdentity import ru.dbotthepony.mc.otm.storage.IStorageIdentity
import ru.dbotthepony.mc.otm.storage.IStorageStack import ru.dbotthepony.mc.otm.storage.IStorageStack
import ru.dbotthepony.mc.otm.storage.VirtualComponent import ru.dbotthepony.mc.otm.storage.VirtualComponent
import javax.annotation.ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
class StorageNetworkGraph : Abstract6Graph<IStorageGraphNode>() { class StorageNetworkGraph : Abstract6Graph<IStorageGraphNode>() {
private val virtualComponents = HashMap<Class<out IStorageStack>, VirtualComponent<out IStorageStack>>() private val virtualComponents = HashMap<Class<out IStorageStack>, VirtualComponent<out IStorageStack>>()
@ -21,9 +17,7 @@ class StorageNetworkGraph : Abstract6Graph<IStorageGraphNode>() {
} }
/** /**
* @param type Class, representing identity of stored object * Returns a [VirtualComponent] representing [type] storage
* @param <T> The identity itself
* @return a virtual IStorageComponent<T>
*/ */
fun <T : IStorageStack> getVirtualComponent(type: Class<T>): VirtualComponent<T> { fun <T : IStorageStack> getVirtualComponent(type: Class<T>): VirtualComponent<T> {
return virtualComponents.computeIfAbsent(type) { VirtualComponent(type) } as VirtualComponent<T> return virtualComponents.computeIfAbsent(type) { VirtualComponent(type) } as VirtualComponent<T>
@ -37,37 +31,17 @@ class StorageNetworkGraph : Abstract6Graph<IStorageGraphNode>() {
getVirtualComponent(identity.storageIdentity()).remove(identity) getVirtualComponent(identity.storageIdentity()).remove(identity)
} }
override fun onNodeRemoved(node: Graph6Node<IStorageGraphNode>) {
node.value.storageGrid = null
for (identity in node.value.getComponents()) {
remove(identity)
}
}
override fun onNodeAdded(node: Graph6Node<IStorageGraphNode>) { override fun onNodeAdded(node: Graph6Node<IStorageGraphNode>) {
node.value.storageGrid = this
for (identity in node.value.getComponents()) { for (identity in node.value.getComponents()) {
add(identity) add(identity)
} }
} }
fun detach(node: Graph6Node<IStorageGraphNode>) { override fun onNodeRemoved(node: Graph6Node<IStorageGraphNode>) {
if (_nodes.contains(node)) {
for (identity in node.value.getComponents()) { for (identity in node.value.getComponents()) {
remove(identity) remove(identity)
} }
} }
}
fun attach(node: Graph6Node<IStorageGraphNode>) {
if (_nodes.contains(node)) {
for (identity in node.value.getComponents()) {
add(identity)
}
}
}
companion object { companion object {
@JvmStatic @JvmStatic

View File

@ -21,7 +21,7 @@ class ItemMonitorMenu @JvmOverloads constructor(
init { init {
if (tile != null) { if (tile != null) {
view.setComponent(tile.cell.storageGrid!!.getVirtualComponent(ItemStackWrapper::class.java)) view.setComponent(tile.cell.getStorageGraph()!!.getVirtualComponent(ItemStackWrapper::class.java))
} }
addBatterySlot() addBatterySlot()

View File

@ -143,14 +143,24 @@ interface IStorageListener<T : IStorageStack> {
} }
interface IStorageTuple<T : IStorageStack> { interface IStorageTuple<T : IStorageStack> {
val id: UUID
get() = id()
val stack: T
get() = stack()
fun id(): UUID fun id(): UUID
fun stack(): T fun stack(): T
} }
@JvmRecord data class StorageTuple<T : IStorageStack>(override val id: UUID, override val stack: T) : IStorageTuple<T> {
data class StorageTuple<T : IStorageStack>(private val id: UUID, private val stack: T) : IStorageTuple<T> { override fun id(): UUID {
override fun id(): UUID = id return id
override fun stack(): T = stack }
override fun stack(): T {
return stack
}
} }
interface IStorageComponent<T : IStorageStack> : IStorageView<T>, IStorageConsumer<T> interface IStorageComponent<T : IStorageStack> : IStorageView<T>, IStorageConsumer<T>