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

View File

@ -1,30 +1,24 @@
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;
package ru.dbotthepony.mc.otm.capability.drive
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:
@ -35,235 +29,279 @@ import java.util.function.Supplier;
* 0. This data can get very large very quickly, even when playing singleplayer (and much quicker and bigger when hosting a dedicated server)
* 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
* 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
* 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/singleplayer worlds
* 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
@ParametersAreNonnullByDefault
public class DrivePool {
private static final Logger LOGGER = LogManager.getLogger();
@Suppress("unused")
object DrivePool {
private val 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 {
public final UUID id;
public final IMatteryDrive<?> drive;
public long last_access = System.currentTimeMillis();
@JvmStatic
operator fun <T : IMatteryDrive<*>> get(
id: UUID,
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) {
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);
val get = pool[id]
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()) {
try {
var factorize = factory_stored.apply(NbtIo.readCompressed(file));
pool.put(id, new Entry(id, factorize));
return factorize;
} catch(Throwable error) {
var report = new CrashReport("Reading OTM Drive from disk", error);
var category = report.addCategory("Corrupt drive");
category.setDetail("UUID", id);
throw new ReportedException(report);
val factorize = factory_stored(NbtIo.readCompressed(file))
pool[id] = WeakDriveReference(factorize)
return factorize
} catch (error: Throwable) {
val report = CrashReport("Reading OTM Drive from disk", error)
val category = report.addCategory("Corrupt drive")
category.setDetail("UUID", id)
throw ReportedException(report)
}
}
var factorize = factory_empty.get();
pool.put(id, new Entry(id, factorize));
return factorize;
val factorize = factory_empty()
pool[id] = WeakDriveReference(factorize)
return factorize
}
public static void put(UUID id, IMatteryDrive<?> drive) {
pool.put(id, new Entry(id, drive));
@JvmStatic
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) {
@Override
public boolean equals(Object obj) {
return obj instanceof BacklogLine line && line.file.equals(file) || obj instanceof UUID uuid && uuid.equals(file);
}
@JvmStatic
fun markDirty(id: UUID) {
if (!isLegalAccess())
throw IllegalAccessException("Can not access drive pool from outside of server thread.")
pool[id]?.access()
}
private static ArrayList<BacklogLine> backlog = new ArrayList<>();
private static File base_directory;
private var backlog = ArrayList<BacklogLine>()
private var baseDirectory: File? = null
private var serverThread: Thread? = null
private static void thread(MinecraftServer server) {
private fun thread(server: MinecraftServer) {
while (true) {
LockSupport.park();
LockSupport.park()
if (stopping) {
return;
return
}
try {
// TODO: Syncing status with main thread, since server stop can occur during work.
sync();
} catch (ReportedException error) {
exception = error;
return;
sync()
} catch (error: ReportedException) {
exception = error
return
}
}
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public static void onServerPostTick(TickEvent.ServerTickEvent event) {
fun onServerPostTick(event: ServerTickEvent) {
if (event.phase == TickEvent.Phase.END) {
if (exception != null) {
throw exception;
throw exception!!
}
}
}
@SubscribeEvent(priority = EventPriority.HIGHEST)
public static void serverStartEvent(ServerStartingEvent event) {
if (thread != null && thread.isAlive()) {
LOGGER.error("FMLServerStartedEvent fired twice.");
LOGGER.error("Attempting to start another DrivePool I/O thread when already running one!");
return;
}
pool.clear();
final var server = event.getServer();
base_directory = new File(server.storageSource.getWorldDir().toFile(), DATA_PATH);
base_directory.mkdirs();
stopping = false;
thread = new Thread(null, () -> DrivePool.thread(server), "Overdrive That Matters DrivePool IO");
thread.start();
/**
* 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)
public static void serverStopEvent(ServerStoppingEvent event) {
if (thread != null && thread.isAlive()) {
stopping = true;
LockSupport.unpark(thread);
fun serverStartEvent(event: ServerAboutToStartEvent) {
if (thread != null && thread!!.isAlive) {
LOGGER.error("FMLServerStartedEvent fired twice.")
LOGGER.error("Attempting to start another DrivePool I/O thread when already running one!")
return
}
pool.clear()
val server = event.server
baseDirectory = File(server.storageSource.worldDir.toFile(), DATA_PATH)
baseDirectory!!.mkdirs()
serverThread = Thread.currentThread()
stopping = false
thread = Thread(null, { thread(server) }, "Overdrive That Matters DrivePool IO")
thread!!.start()
}
@SubscribeEvent(priority = EventPriority.HIGHEST)
fun serverStopEvent(event: ServerStoppingEvent) {
val thread = thread
if (thread != null && thread.isAlive) {
stopping = true
LockSupport.unpark(thread)
}
if (exception == null) {
writeBacklog();
sync();
writeBacklog()
sync()
}
pool.clear();
pool.clear()
}
@SubscribeEvent
public static void onWorldSave(WorldEvent.Save event) {
writeBacklog();
fun onWorldSave(event: WorldEvent.Save) {
writeBacklog()
}
public static void writeBacklog() {
var sync_required = false;
ArrayList<UUID> remove_keys = null;
private fun writeBacklog() {
var needsSync = false
var removeKeys: ArrayList<UUID>? = null
for (var entry : pool.entrySet()) {
var tag = entry.getValue().sync();
for ((key, value) in pool) {
val tag = value.sync()
if (tag != null) {
LOGGER.info("Serializing OTM Drive {}", entry.getKey());
var index = backlog.indexOf(entry.getKey());
LOGGER.info("Serializing OTM Drive {}", key)
val index = backlog.indexOf<Any>(key)
if (index != -1) {
backlog.set(index, new BacklogLine(entry.getKey(), tag, entry.getValue().drive));
backlog[index] = BacklogLine(key, tag, value.drive()!!)
} else {
backlog.add(new BacklogLine(entry.getKey(), tag, entry.getValue().drive));
backlog.add(BacklogLine(key, tag, value.drive()!!))
}
sync_required = true;
} else if (entry.getValue().last_access < System.currentTimeMillis() - 600_000) {
// no access or changes in 600 seconds - unload
if (remove_keys == null)
remove_keys = new ArrayList<>();
needsSync = true
} else if (!value.valid()) {
if (removeKeys == null)
removeKeys = ArrayList()
remove_keys.add(entry.getKey());
removeKeys.add(key)
}
}
if (remove_keys != null) {
for (var key : remove_keys) {
pool.remove(key);
if (removeKeys != null) {
for (key in removeKeys) {
pool.remove(key)
}
}
if (sync_required) {
LockSupport.unpark(thread);
if (needsSync) {
LockSupport.unpark(thread)
}
}
public static void sync() {
var copy = backlog;
backlog = new ArrayList<>();
private fun sync() {
val copy = backlog
backlog = ArrayList()
for (var entry : copy) {
for (entry in copy) {
try {
LOGGER.info("Syncing OTM Drive {}", entry.file);
var old_file = new File(base_directory, entry.file + ".dat_old");
LOGGER.info("Syncing OTM Drive {}", entry.file)
if (old_file.exists()) {
if (!old_file.delete()) {
throw new IllegalStateException("Unable to delete old dat file");
}
val oldFile = File(baseDirectory, entry.file.toString() + ".dat_old")
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 (!file.renameTo(old_file)) {
throw new IllegalStateException("Unable to move old dat file");
}
if (newFile.exists()) {
check(newFile.renameTo(oldFile)) { "Unable to move old dat file" }
}
NbtIo.writeCompressed(entry.tag, file);
} catch(Throwable error) {
var report = new CrashReport("Syncing OTM Drives state to disk", error);
var category = report.addCategory("Corrupt drive");
category.setDetail("UUID", entry.file.toString());
category.setDetail("Stored item count", entry.drive.getStoredCount());
category.setDetail("Capacity", entry.drive.getCapacity());
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);
NbtIo.writeCompressed(entry.tag, newFile)
} catch (error: Throwable) {
val report = CrashReport("Syncing OTM Drives state to disk", error)
val category = report.addCategory("Corrupt drive")
category.setDetail("UUID", entry.file.toString())
category.setDetail("Stored item count", entry.drive.storedCount)
category.setDetail("Capacity", entry.drive.driveCapacity)
throw 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.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraftforge.registries.RegistryManager;
import ru.dbotthepony.mc.otm.core.Fraction;
import ru.dbotthepony.mc.otm.storage.IStorageTuple;
import ru.dbotthepony.mc.otm.storage.ItemStackWrapper;
import ru.dbotthepony.mc.otm.storage.StorageObjectRegistry;
import ru.dbotthepony.mc.otm.storage.StorageObjectTuple;
import net.minecraft.nbt.CompoundTag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraftforge.registries.RegistryManager
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.ItemStackWrapper
import ru.dbotthepony.mc.otm.storage.StorageObjectRegistry
import ru.dbotthepony.mc.otm.storage.StorageObjectTuple
import java.util.*
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
class ItemMatteryDrive : AbstractMatteryDrive<ItemStackWrapper>, IItemMatteryDrive {
constructor(capacity: Fraction, max_different_stacks: Int) : super(capacity, maxDifferentStacks = max_different_stacks)
constructor(capacity: Fraction, uuid: UUID, max_different_stacks: Int) : super(capacity, uuid, max_different_stacks)
constructor(capacity: Fraction, uuid: UUID) : super(capacity, uuid)
constructor(capacity: Fraction) : super(capacity)
@MethodsReturnNonnullByDefault
@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() {
override fun identity(): StorageObjectTuple<ItemStackWrapper> {
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) {
return insertStack(new ItemStackWrapper(item), simulate).stack();
fun insertObject(item: ItemStack?, simulate: Boolean): ItemStack {
return insertStack(ItemStackWrapper(item!!), simulate).stack
}
@Override
protected CompoundTag serializeStack(IStorageTuple<ItemStackWrapper> item) {
final var tag = new CompoundTag();
final var location = Objects.requireNonNull(item.stack().stack().getItem().getRegistryName(), "Missing registry name for stored Item").toString();
override fun serializeStack(item: IStorageTuple<ItemStackWrapper>): CompoundTag? {
val tag = CompoundTag()
val location = item.stack().stack.item.registryName!!.toString()
tag.putString("item", location);
tag.putInt("count", item.stack().stack().getCount());
tag["item"] = location
tag["count"] = item.stack.stack.count
CompoundTag item_tag;
val itag = item.stack.stack.tag
if ((item_tag = item.stack().stack().getTag()) != null) {
tag.put("data", item_tag);
if (itag != null) {
tag["data"] = itag
}
return tag;
return tag
}
@Nullable
@Override
protected ItemStackWrapper deserializeStack(CompoundTag tag) {
final var item = RegistryManager.ACTIVE.getRegistry(Item.class).getValue(new ResourceLocation(tag.getString("item")));
override fun deserializeStack(tag: CompoundTag): ItemStackWrapper? {
val item = RegistryManager.ACTIVE.getRegistry(Item::class.java).getValue(ResourceLocation(tag.getString("item")))
if (item != null && item != Items.AIR) {
final var count = tag.getInt("count");
final var data = tag.get("data");
final var itemstack = new ItemStack(item, count);
if (data != null) {
itemstack.setTag((CompoundTag) data);
}
return new ItemStackWrapper(itemstack);
if (item != null && item !== Items.AIR) {
val count = tag.getInt("count")
val itemstack = ItemStack(item, count)
itemstack.tag = tag["data"] as? CompoundTag?
return ItemStackWrapper(itemstack)
}
return null;
return null
}
@Override
public List<IStorageTuple<ItemStackWrapper>> findItems(Item item) {
var list = items.get(item);
if (list != null)
return List.copyOf(list);
return List.of();
override fun findItems(item: Item): List<IStorageTuple<ItemStackWrapper>> {
val list = storedStacks[item]
return if (list != null) java.util.List.copyOf(list) else emptyList()
}
@Override
public List<IStorageTuple<ItemStackWrapper>> findItems(ItemStack stack) {
var list = items.get(stack.getItem());
override fun findItems(stack: ItemStack): List<IStorageTuple<ItemStackWrapper>> {
val list = storedStacks[stack.item] ?: return emptyList()
if (list == null)
return List.of();
var amount = 0
var amount = 0;
for (var _stack : list) {
if (ItemStack.tagMatches(_stack.stack().stack(), stack)) {
amount++;
for (_stack in list) {
if (ItemStack.tagMatches(_stack.stack.stack, stack)) {
amount++
}
}
var build_list = new ArrayList<IStorageTuple<ItemStackWrapper>>(amount);
var i = 0;
val build_list = ArrayList<IStorageTuple<ItemStackWrapper>>(amount)
var i = 0
for (var _stack : list) {
if (ItemStack.tagMatches(_stack.stack().stack(), stack)) {
build_list.set(i, _stack);
i++;
for (_stack in list) {
if (ItemStack.tagMatches(_stack.stack.stack, stack)) {
build_list[i] = _stack
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)
attachStorage()
getStorageGraph()?.add(component)
}
fun removeStorageComponent(component: IStorageIdentity<*>) {
for (component1 in components) {
if (component === component1 || component1.storageIdentity() == component.storageIdentity()) {
detachStorage()
components.remove(component1)
attachStorage()
getStorageGraph()?.remove(component)
return
}
}
@ -85,6 +83,4 @@ open class BasicStorageGraphNode : IStorageGraphNode {
fun get(): LazyOptional<IStorageGraphNode> {
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.
*
* If you don't know what you are going to store, return empty list, then, when you make up your
* mind by either player tweaking settings or something,
* call helper method detachStorage();
* add whatever going to end up in getComponents();
* add helper method attachStorage();
* If you don't know what you are going to store, you may return nothing.
*
* Failing to do it in this order will cause IllegalStateException
*
* @return a list of all providers, inheriting IStorageIdentity, or an empty list
* If something pops up, call [getStorageGraph], and then, call [StorageNetworkGraph.add].
*/
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 getStorageGraph(): StorageNetworkGraph? = getAsStorageNode().graph as StorageNetworkGraph?
}

View File

@ -1,6 +1,5 @@
package ru.dbotthepony.mc.otm.graph.storage
import net.minecraft.MethodsReturnNonnullByDefault
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.block.entity.BlockEntity
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.IStorageStack
import ru.dbotthepony.mc.otm.storage.VirtualComponent
import javax.annotation.ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
class StorageNetworkGraph : Abstract6Graph<IStorageGraphNode>() {
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
* @param <T> The identity itself
* @return a virtual IStorageComponent<T>
* Returns a [VirtualComponent] representing [type] storage
*/
fun <T : IStorageStack> getVirtualComponent(type: Class<T>): VirtualComponent<T> {
return virtualComponents.computeIfAbsent(type) { VirtualComponent(type) } as VirtualComponent<T>
@ -37,35 +31,15 @@ class StorageNetworkGraph : Abstract6Graph<IStorageGraphNode>() {
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>) {
node.value.storageGrid = this
for (identity in node.value.getComponents()) {
add(identity)
}
}
fun detach(node: Graph6Node<IStorageGraphNode>) {
if (_nodes.contains(node)) {
for (identity in node.value.getComponents()) {
remove(identity)
}
}
}
fun attach(node: Graph6Node<IStorageGraphNode>) {
if (_nodes.contains(node)) {
for (identity in node.value.getComponents()) {
add(identity)
}
override fun onNodeRemoved(node: Graph6Node<IStorageGraphNode>) {
for (identity in node.value.getComponents()) {
remove(identity)
}
}

View File

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

View File

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