// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.component;

import com.hypixel.hytale.component.data.unknown.TempUnknownComponent;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
import java.util.Map;
import java.util.Arrays;
import java.util.Optional;
import org.bson.BsonValue;
import org.bson.BsonDocument;
import java.lang.ref.WeakReference;
import java.util.function.Consumer;
import com.hypixel.hytale.component.spatial.SpatialResource;
import com.hypixel.hytale.component.spatial.SpatialStructure;
import java.util.Iterator;
import com.hypixel.hytale.component.system.WorldEventSystem;
import com.hypixel.hytale.component.system.EntityEventSystem;
import com.hypixel.hytale.component.system.System;
import com.hypixel.hytale.common.util.ArrayUtil;
import java.util.logging.Level;
import com.hypixel.hytale.component.query.ReadWriteArchetypeQuery;
import com.hypixel.hytale.component.data.change.SystemGroupChange;
import com.hypixel.hytale.component.dependency.Dependency;
import java.util.Collections;
import com.hypixel.hytale.component.event.WorldEventType;
import com.hypixel.hytale.component.event.EntityEventType;
import com.hypixel.hytale.component.data.change.SystemTypeChange;
import com.hypixel.hytale.component.data.change.ResourceChange;
import com.hypixel.hytale.component.query.Query;
import java.util.List;
import com.hypixel.hytale.component.data.change.SystemChange;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import com.hypixel.hytale.component.data.change.ComponentChange;
import com.hypixel.hytale.component.data.change.ChangeType;
import com.hypixel.hytale.component.data.change.DataChange;
import com.hypixel.hytale.codec.Codec;
import java.util.HashSet;
import com.hypixel.hytale.codec.ExtraInfo;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import com.hypixel.hytale.component.system.tick.ArchetypeTickingSystem;
import com.hypixel.hytale.component.system.tick.RunWhenPausedSystem;
import com.hypixel.hytale.component.system.tick.TickableSystem;
import com.hypixel.hytale.component.system.tick.TickingSystem;
import com.hypixel.hytale.component.system.QuerySystem;
import com.hypixel.hytale.component.system.RefChangeSystem;
import com.hypixel.hytale.component.system.RefSystem;
import com.hypixel.hytale.component.system.HolderSystem;
import com.hypixel.hytale.component.data.unknown.UnknownComponents;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.Reference;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import com.hypixel.hytale.component.system.EcsEvent;
import com.hypixel.hytale.component.system.ISystem;
import java.util.function.Supplier;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import java.util.BitSet;
import javax.annotation.Nonnull;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.concurrent.locks.StampedLock;
import java.util.concurrent.atomic.AtomicInteger;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.logger.HytaleLogger;

public class ComponentRegistry<ECS_TYPE> implements IComponentRegistry<ECS_TYPE>
{
    public static final int UNASSIGNED_INDEX = Integer.MIN_VALUE;
    public static final int DEFAULT_INITIAL_SIZE = 16;
    private static final HytaleLogger LOGGER;
    @Deprecated
    private static final KeyedCodec<Integer> VERSION;
    private static final AtomicInteger REFERENCE_THREAD_COUNTER;
    private boolean shutdown;
    private final StampedLock dataLock;
    @Nonnull
    private final Object2IntMap<String> componentIdToIndex;
    private final BitSet componentIndexReuse;
    private int componentSize;
    @Nonnull
    private String[] componentIds;
    @Nonnull
    private BuilderCodec<? extends Component<ECS_TYPE>>[] componentCodecs;
    @Nonnull
    private Supplier<? extends Component<ECS_TYPE>>[] componentSuppliers;
    @Nonnull
    private ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>[] componentTypes;
    @Nonnull
    private final Object2IntMap<String> resourceIdToIndex;
    private final BitSet resourceIndexReuse;
    private int resourceSize;
    @Nonnull
    private String[] resourceIds;
    @Nonnull
    private BuilderCodec<? extends Resource<ECS_TYPE>>[] resourceCodecs;
    @Nonnull
    private Supplier<? extends Resource<ECS_TYPE>>[] resourceSuppliers;
    @Nonnull
    private ResourceType<ECS_TYPE, ? extends Resource<ECS_TYPE>>[] resourceTypes;
    @Nonnull
    private final Object2IntMap<Class<? extends ISystem<ECS_TYPE>>> systemTypeClassToIndex;
    @Nonnull
    private final Object2IntMap<Class<? extends EcsEvent>> entityEventTypeClassToIndex;
    @Nonnull
    private final Object2IntMap<Class<? extends EcsEvent>> worldEventTypeClassToIndex;
    private final BitSet systemTypeIndexReuse;
    private int systemTypeSize;
    @Nonnull
    private SystemType<ECS_TYPE, ? extends ISystem<ECS_TYPE>>[] systemTypes;
    private BitSet[] systemTypeToSystemIndex;
    private final BitSet systemGroupIndexReuse;
    private int systemGroupSize;
    @Nonnull
    private SystemGroup<ECS_TYPE>[] systemGroups;
    private int systemSize;
    @Nonnull
    private ISystem<ECS_TYPE>[] systems;
    @Nonnull
    private ISystem<ECS_TYPE>[] sortedSystems;
    @Nonnull
    private final Object2IntMap<Class<? extends ISystem<ECS_TYPE>>> systemClasses;
    @Nonnull
    private final Object2BooleanMap<Class<? extends ISystem<ECS_TYPE>>> systemBypassClassCheck;
    @Nonnull
    private final StampedLock storeLock;
    private int storeSize;
    @Nonnull
    private Store<ECS_TYPE>[] stores;
    private final ReadWriteLock dataUpdateLock;
    private Data<ECS_TYPE> data;
    private final Set<Reference<Holder<ECS_TYPE>>> holders;
    private final ReferenceQueue<Holder<ECS_TYPE>> holderReferenceQueue;
    @Nonnull
    private final Thread holderReferenceThread;
    @Nonnull
    private final ComponentType<ECS_TYPE, UnknownComponents<ECS_TYPE>> unknownComponentType;
    @Nonnull
    private final ComponentType<ECS_TYPE, NonTicking<ECS_TYPE>> nonTickingComponentType;
    @Nonnull
    private final ComponentType<ECS_TYPE, NonSerialized<ECS_TYPE>> nonSerializedComponentType;
    @Nonnull
    private final SystemType<ECS_TYPE, HolderSystem<ECS_TYPE>> holderSystemType;
    @Nonnull
    private final SystemType<ECS_TYPE, RefSystem<ECS_TYPE>> refSystemType;
    @Nonnull
    private final SystemType<ECS_TYPE, RefChangeSystem<ECS_TYPE, ?>> refChangeSystemType;
    @Nonnull
    private final SystemType<ECS_TYPE, QuerySystem<ECS_TYPE>> querySystemType;
    @Nonnull
    private final SystemType<ECS_TYPE, TickingSystem<ECS_TYPE>> tickingSystemType;
    @Nonnull
    private final SystemType<ECS_TYPE, TickableSystem<ECS_TYPE>> tickableSystemType;
    @Nonnull
    private final SystemType<ECS_TYPE, RunWhenPausedSystem<ECS_TYPE>> runWhenPausedSystemType;
    @Nonnull
    private final SystemType<ECS_TYPE, ArchetypeTickingSystem<ECS_TYPE>> archetypeTickingSystemType;
    
    public ComponentRegistry() {
        this.dataLock = new StampedLock();
        this.componentIdToIndex = new Object2IntOpenHashMap<String>(16);
        this.componentIndexReuse = new BitSet();
        this.componentIds = new String[16];
        this.componentCodecs = new BuilderCodec[16];
        this.componentSuppliers = new Supplier[16];
        this.componentTypes = new ComponentType[16];
        this.resourceIdToIndex = new Object2IntOpenHashMap<String>(16);
        this.resourceIndexReuse = new BitSet();
        this.resourceIds = new String[16];
        this.resourceCodecs = new BuilderCodec[16];
        this.resourceSuppliers = new Supplier[16];
        this.resourceTypes = new ResourceType[16];
        this.systemTypeClassToIndex = new Object2IntOpenHashMap<Class<? extends ISystem<ECS_TYPE>>>(16);
        this.entityEventTypeClassToIndex = new Object2IntOpenHashMap<Class<? extends EcsEvent>>(16);
        this.worldEventTypeClassToIndex = new Object2IntOpenHashMap<Class<? extends EcsEvent>>(16);
        this.systemTypeIndexReuse = new BitSet();
        this.systemTypes = new SystemType[16];
        this.systemTypeToSystemIndex = new BitSet[16];
        this.systemGroupIndexReuse = new BitSet();
        this.systemGroups = new SystemGroup[16];
        this.systems = new ISystem[16];
        this.sortedSystems = new ISystem[16];
        this.systemClasses = new Object2IntOpenHashMap<Class<? extends ISystem<ECS_TYPE>>>(16);
        this.systemBypassClassCheck = new Object2BooleanOpenHashMap<Class<? extends ISystem<ECS_TYPE>>>(16);
        this.storeLock = new StampedLock();
        this.stores = new Store[16];
        this.dataUpdateLock = new ReentrantReadWriteLock();
        this.holders = (Set<Reference<Holder<ECS_TYPE>>>)ConcurrentHashMap.newKeySet();
        this.holderReferenceQueue = new ReferenceQueue<Holder<ECS_TYPE>>();
        this.componentIdToIndex.defaultReturnValue(Integer.MIN_VALUE);
        this.resourceIdToIndex.defaultReturnValue(Integer.MIN_VALUE);
        this.systemTypeClassToIndex.defaultReturnValue(Integer.MIN_VALUE);
        this.entityEventTypeClassToIndex.defaultReturnValue(Integer.MIN_VALUE);
        this.worldEventTypeClassToIndex.defaultReturnValue(Integer.MIN_VALUE);
        for (int i = 0; i < 16; ++i) {
            this.systemTypeToSystemIndex[i] = new BitSet();
        }
        this.data = new Data<ECS_TYPE>(this);
        this.unknownComponentType = (ComponentType<ECS_TYPE, UnknownComponents<ECS_TYPE>>)this.registerComponent(UnknownComponents.class, "Unknown", UnknownComponents.CODEC);
        this.nonTickingComponentType = (ComponentType<ECS_TYPE, NonTicking<ECS_TYPE>>)this.registerComponent(NonTicking.class, NonTicking::get);
        this.nonSerializedComponentType = (ComponentType<ECS_TYPE, NonSerialized<ECS_TYPE>>)this.registerComponent(NonSerialized.class, NonSerialized::get);
        this.holderSystemType = this.registerSystemType((Class<? super HolderSystem<ECS_TYPE>>)HolderSystem.class);
        this.refSystemType = this.registerSystemType((Class<? super RefSystem<ECS_TYPE>>)RefSystem.class);
        this.refChangeSystemType = this.registerSystemType((Class<? super RefChangeSystem<ECS_TYPE, ?>>)RefChangeSystem.class);
        this.querySystemType = this.registerSystemType((Class<? super QuerySystem<ECS_TYPE>>)QuerySystem.class);
        this.tickingSystemType = this.registerSystemType((Class<? super TickingSystem<ECS_TYPE>>)TickingSystem.class);
        this.tickableSystemType = this.registerSystemType((Class<? super TickableSystem<ECS_TYPE>>)TickableSystem.class);
        this.runWhenPausedSystemType = this.registerSystemType((Class<? super RunWhenPausedSystem<ECS_TYPE>>)RunWhenPausedSystem.class);
        this.archetypeTickingSystemType = this.registerSystemType((Class<? super ArchetypeTickingSystem<ECS_TYPE>>)ArchetypeTickingSystem.class);
        (this.holderReferenceThread = new Thread(() -> {
            try {
                while (!Thread.interrupted()) {
                    this.holders.remove(this.holderReferenceQueue.remove());
                }
            }
            catch (final InterruptedException ignored) {
                Thread.currentThread().interrupt();
            }
            return;
        }, "EntityHolderReferenceThread-" + ComponentRegistry.REFERENCE_THREAD_COUNTER.getAndIncrement())).setDaemon(true);
        this.holderReferenceThread.start();
    }
    
    public boolean isShutdown() {
        return this.shutdown;
    }
    
    public void shutdown() {
        this.shutdown0();
    }
    
    void shutdown0() {
        this.shutdown = true;
        this.holderReferenceThread.interrupt();
        final long lock = this.storeLock.writeLock();
        try {
            for (int storeIndex = this.storeSize - 1; storeIndex >= 0; --storeIndex) {
                final Store<ECS_TYPE> store = this.stores[storeIndex];
                if (store != null) {
                    store.shutdown0(this.data);
                }
            }
            this.stores = Store.EMPTY_ARRAY;
        }
        finally {
            this.storeLock.unlockWrite(lock);
        }
    }
    
    @Nonnull
    public ReadWriteLock getDataUpdateLock() {
        return this.dataUpdateLock;
    }
    
    @Nonnull
    public ComponentType<ECS_TYPE, UnknownComponents<ECS_TYPE>> getUnknownComponentType() {
        return this.unknownComponentType;
    }
    
    @Nonnull
    public ComponentType<ECS_TYPE, NonTicking<ECS_TYPE>> getNonTickingComponentType() {
        return this.nonTickingComponentType;
    }
    
    @Nonnull
    public ComponentType<ECS_TYPE, NonSerialized<ECS_TYPE>> getNonSerializedComponentType() {
        return this.nonSerializedComponentType;
    }
    
    @Nonnull
    public SystemType<ECS_TYPE, HolderSystem<ECS_TYPE>> getHolderSystemType() {
        return this.holderSystemType;
    }
    
    @Nonnull
    public SystemType<ECS_TYPE, RefSystem<ECS_TYPE>> getRefSystemType() {
        return this.refSystemType;
    }
    
    @Nonnull
    public SystemType<ECS_TYPE, RefChangeSystem<ECS_TYPE, ?>> getRefChangeSystemType() {
        return this.refChangeSystemType;
    }
    
    @Nonnull
    public SystemType<ECS_TYPE, QuerySystem<ECS_TYPE>> getQuerySystemType() {
        return this.querySystemType;
    }
    
    @Nonnull
    public SystemType<ECS_TYPE, TickingSystem<ECS_TYPE>> getTickingSystemType() {
        return this.tickingSystemType;
    }
    
    @Nonnull
    public SystemType<ECS_TYPE, TickableSystem<ECS_TYPE>> getTickableSystemType() {
        return this.tickableSystemType;
    }
    
    @Nonnull
    public SystemType<ECS_TYPE, RunWhenPausedSystem<ECS_TYPE>> getRunWhenPausedSystemType() {
        return this.runWhenPausedSystemType;
    }
    
    @Nonnull
    public SystemType<ECS_TYPE, ArchetypeTickingSystem<ECS_TYPE>> getArchetypeTickingSystemType() {
        return this.archetypeTickingSystemType;
    }
    
    @Nonnull
    @Override
    public <T extends Component<ECS_TYPE>> ComponentType<ECS_TYPE, T> registerComponent(@Nonnull final Class<? super T> tClass, @Nonnull final Supplier<T> supplier) {
        return this.registerComponent(tClass, null, null, supplier, false);
    }
    
    @Nonnull
    @Override
    public <T extends Component<ECS_TYPE>> ComponentType<ECS_TYPE, T> registerComponent(@Nonnull final Class<? super T> tClass, @Nonnull final String id, @Nonnull final BuilderCodec<T> codec) {
        Objects.requireNonNull(codec);
        return this.registerComponent(tClass, id, codec, codec::getDefaultValue, false);
    }
    
    @Deprecated
    @Nonnull
    public <T extends Component<ECS_TYPE>> ComponentType<ECS_TYPE, T> registerComponent(@Nonnull final Class<? super T> tClass, @Nonnull final String id, @Nonnull final BuilderCodec<T> codec, final boolean skipValidation) {
        Objects.requireNonNull(codec);
        return this.registerComponent(tClass, id, codec, codec::getDefaultValue, skipValidation);
    }
    
    @Nonnull
    private <T extends Component<ECS_TYPE>> ComponentType<ECS_TYPE, T> registerComponent(@Nonnull final Class<? super T> tClass, @Nullable final String id, @Nullable final BuilderCodec<T> codec, @Nonnull final Supplier<T> supplier, final boolean skipValidation) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        if (codec != null && !skipValidation) {
            final ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get();
            codec.validateDefaults(extraInfo, new HashSet<Codec<?>>());
            extraInfo.getValidationResults().logOrThrowValidatorExceptions(ComponentRegistry.LOGGER, "Default Asset Validation Failed!\n");
        }
        long lock = this.dataLock.writeLock();
        try {
            final ComponentType<ECS_TYPE, T> componentType = this.registerComponent0(tClass, id, codec, supplier, new ComponentType<ECS_TYPE, T>());
            lock = this.dataLock.tryConvertToReadLock(lock);
            this.updateData0(new ComponentChange<Object, Object>(ChangeType.REGISTERED, componentType));
            return componentType;
        }
        finally {
            this.dataLock.unlock(lock);
        }
    }
    
    public <T extends Component<ECS_TYPE>> void unregisterComponent(@Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        componentType.validateRegistry(this);
        componentType.validate();
        if (componentType.equals(this.unknownComponentType)) {
            throw new IllegalArgumentException("UnknownComponentType can not be unregistered!");
        }
        long lock = this.dataLock.writeLock();
        try {
            this.unregisterComponent0((ComponentType<ECS_TYPE, Component>)componentType);
            final List<DataChange> changes = new ObjectArrayList<DataChange>();
            changes.add(new ComponentChange<Object, Object>(ChangeType.UNREGISTERED, (ComponentType<Object, Object>)componentType));
            for (int unsortedSystemIndex = this.systemSize - 1; unsortedSystemIndex >= 0; --unsortedSystemIndex) {
                final ISystem<ECS_TYPE> system = this.systems[unsortedSystemIndex];
                if (system instanceof final QuerySystem querySystem) {
                    final QuerySystem<ECS_TYPE> archetypeSystem = querySystem;
                    final Query<ECS_TYPE> query = archetypeSystem.getQuery();
                    if (query != null && query.requiresComponentType(componentType)) {
                        this.unregisterSystem0(unsortedSystemIndex, system);
                        changes.add(new SystemChange<Object>(ChangeType.UNREGISTERED, (ISystem<Object>)system));
                    }
                }
            }
            lock = this.dataLock.tryConvertToReadLock(lock);
            this.updateData0((DataChange[])changes.toArray(DataChange[]::new));
            componentType.invalidate();
        }
        finally {
            this.dataLock.unlock(lock);
        }
    }
    
    @Nonnull
    @Override
    public <T extends Resource<ECS_TYPE>> ResourceType<ECS_TYPE, T> registerResource(@Nonnull final Class<? super T> tClass, @Nonnull final Supplier<T> supplier) {
        return this.registerResource(tClass, null, null, supplier);
    }
    
    @Nonnull
    @Override
    public <T extends Resource<ECS_TYPE>> ResourceType<ECS_TYPE, T> registerResource(@Nonnull final Class<? super T> tClass, @Nonnull final String id, @Nonnull final BuilderCodec<T> codec) {
        Objects.requireNonNull(codec);
        return this.registerResource(tClass, id, codec, codec::getDefaultValue);
    }
    
    @Nonnull
    private <T extends Resource<ECS_TYPE>> ResourceType<ECS_TYPE, T> registerResource(@Nonnull final Class<? super T> tClass, @Nullable final String id, @Nullable final BuilderCodec<T> codec, @Nonnull final Supplier<T> supplier) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        if (codec != null) {
            final ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get();
            codec.validateDefaults(extraInfo, new HashSet<Codec<?>>());
            extraInfo.getValidationResults().logOrThrowValidatorExceptions(ComponentRegistry.LOGGER, "Default Asset Validation Failed!\n");
        }
        long lock = this.dataLock.writeLock();
        try {
            final ResourceType<ECS_TYPE, T> resourceType = this.registerResource0(tClass, id, codec, supplier, new ResourceType<ECS_TYPE, T>());
            lock = this.dataLock.tryConvertToReadLock(lock);
            this.updateData0(new ResourceChange<Object, Object>(ChangeType.REGISTERED, resourceType));
            return resourceType;
        }
        finally {
            this.dataLock.unlock(lock);
        }
    }
    
    public <T extends Resource<ECS_TYPE>> void unregisterResource(@Nonnull final ResourceType<ECS_TYPE, T> resourceType) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        resourceType.validateRegistry(this);
        resourceType.validate();
        long lock = this.dataLock.writeLock();
        try {
            this.unregisterResource0((ResourceType<ECS_TYPE, Resource>)resourceType);
            lock = this.dataLock.tryConvertToReadLock(lock);
            this.updateData0(new ResourceChange<Object, Object>(ChangeType.UNREGISTERED, resourceType));
            resourceType.invalidate();
        }
        finally {
            this.dataLock.unlock(lock);
        }
    }
    
    @Nonnull
    @Override
    public <T extends ISystem<ECS_TYPE>> SystemType<ECS_TYPE, T> registerSystemType(@Nonnull final Class<? super T> systemTypeClass) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        if (!ISystem.class.isAssignableFrom(systemTypeClass)) {
            throw new IllegalArgumentException("systemTypeClass must extend ComponentSystem! " + String.valueOf(systemTypeClass));
        }
        long lock = this.dataLock.writeLock();
        try {
            final SystemType<ECS_TYPE, T> systemType = (SystemType<ECS_TYPE, T>)this.registerSystemType0((Class<? super ISystem>)systemTypeClass);
            lock = this.dataLock.tryConvertToReadLock(lock);
            this.updateData0(new SystemTypeChange<Object, Object>(ChangeType.REGISTERED, systemType));
            return systemType;
        }
        finally {
            this.dataLock.unlock(lock);
        }
    }
    
    public <T extends ISystem<ECS_TYPE>> void unregisterSystemType(@Nonnull final SystemType<ECS_TYPE, T> systemType) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        systemType.validate();
        long lock = this.dataLock.writeLock();
        try {
            this.unregisterSystemType0((SystemType<ECS_TYPE, ISystem>)systemType);
            lock = this.dataLock.tryConvertToReadLock(lock);
            this.updateData0(new SystemTypeChange<Object, Object>(ChangeType.UNREGISTERED, systemType));
            systemType.invalidate();
        }
        finally {
            this.dataLock.unlock(lock);
        }
    }
    
    @Nonnull
    @Override
    public <T extends EcsEvent> EntityEventType<ECS_TYPE, T> registerEntityEventType(@Nonnull final Class<? super T> eventTypeClass) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        if (!EcsEvent.class.isAssignableFrom(eventTypeClass)) {
            throw new IllegalArgumentException("eventTypeClass must extend EcsEvent! " + String.valueOf(eventTypeClass));
        }
        long lock = this.dataLock.writeLock();
        try {
            final EntityEventType<ECS_TYPE, T> systemType = (EntityEventType<ECS_TYPE, T>)this.registerEntityEventType0((Class<? super EcsEvent>)eventTypeClass);
            lock = this.dataLock.tryConvertToReadLock(lock);
            this.updateData0(new SystemTypeChange<Object, Object>(ChangeType.REGISTERED, systemType));
            return systemType;
        }
        finally {
            this.dataLock.unlock(lock);
        }
    }
    
    @Nonnull
    @Override
    public <T extends EcsEvent> WorldEventType<ECS_TYPE, T> registerWorldEventType(@Nonnull final Class<? super T> eventTypeClass) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        if (!EcsEvent.class.isAssignableFrom(eventTypeClass)) {
            throw new IllegalArgumentException("eventTypeClass must extend EcsEvent! " + String.valueOf(eventTypeClass));
        }
        long lock = this.dataLock.writeLock();
        try {
            final WorldEventType<ECS_TYPE, T> systemType = (WorldEventType<ECS_TYPE, T>)this.registerWorldEventType0((Class<? super EcsEvent>)eventTypeClass);
            lock = this.dataLock.tryConvertToReadLock(lock);
            this.updateData0(new SystemTypeChange<Object, Object>(ChangeType.REGISTERED, systemType));
            return systemType;
        }
        finally {
            this.dataLock.unlock(lock);
        }
    }
    
    public <T extends EcsEvent> void unregisterEntityEventType(@Nonnull final EntityEventType<ECS_TYPE, T> eventType) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        eventType.validate();
        long lock = this.dataLock.writeLock();
        try {
            this.unregisterEntityEventType0((EntityEventType<ECS_TYPE, EcsEvent>)eventType);
            lock = this.dataLock.tryConvertToReadLock(lock);
            this.updateData0(new SystemTypeChange<Object, Object>(ChangeType.UNREGISTERED, eventType));
            eventType.invalidate();
        }
        finally {
            this.dataLock.unlock(lock);
        }
    }
    
    public <T extends EcsEvent> void unregisterWorldEventType(@Nonnull final WorldEventType<ECS_TYPE, T> eventType) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        eventType.validate();
        long lock = this.dataLock.writeLock();
        try {
            this.unregisterWorldEventType0((WorldEventType<ECS_TYPE, EcsEvent>)eventType);
            lock = this.dataLock.tryConvertToReadLock(lock);
            this.updateData0(new SystemTypeChange<Object, Object>(ChangeType.UNREGISTERED, eventType));
            eventType.invalidate();
        }
        finally {
            this.dataLock.unlock(lock);
        }
    }
    
    @Nonnull
    @Override
    public SystemGroup<ECS_TYPE> registerSystemGroup() {
        return this.registerSystemGroup(Collections.emptySet());
    }
    
    @Nonnull
    public SystemGroup<ECS_TYPE> registerSystemGroup(final Set<Dependency<ECS_TYPE>> dependencies) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        long lock = this.dataLock.writeLock();
        try {
            final SystemGroup<ECS_TYPE> systemGroup = this.registerSystemGroup0(dependencies);
            lock = this.dataLock.tryConvertToReadLock(lock);
            this.updateData0(new SystemGroupChange<Object>(ChangeType.REGISTERED, systemGroup));
            return systemGroup;
        }
        finally {
            this.dataLock.unlock(lock);
        }
    }
    
    public void unregisterSystemGroup(@Nonnull final SystemGroup<ECS_TYPE> systemGroup) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        systemGroup.validate();
        long lock = this.dataLock.writeLock();
        try {
            this.unregisterSystemGroup0(systemGroup);
            lock = this.dataLock.tryConvertToReadLock(lock);
            this.updateData0(new SystemGroupChange<Object>(ChangeType.UNREGISTERED, systemGroup));
            systemGroup.invalidate();
        }
        finally {
            this.dataLock.unlock(lock);
        }
    }
    
    @Override
    public void registerSystem(@Nonnull final ISystem<ECS_TYPE> system) {
        this.registerSystem(system, false);
    }
    
    @Deprecated(forRemoval = true)
    public void registerSystem(@Nonnull final ISystem<ECS_TYPE> system, final boolean bypassClassCheck) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        final Class<? extends ISystem> systemClass = system.getClass();
        if (system instanceof final QuerySystem querySystem) {
            final QuerySystem<ECS_TYPE> archetypeSystem = querySystem;
            final Query<ECS_TYPE> query = archetypeSystem.getQuery();
            query.validateRegistry(this);
            query.validate();
            if (query instanceof final ReadWriteArchetypeQuery readWriteArchetypeQuery) {
                final ReadWriteArchetypeQuery<ECS_TYPE> readWriteQuery = readWriteArchetypeQuery;
                if (readWriteQuery.getReadArchetype().equals(readWriteQuery.getWriteArchetype())) {
                    ComponentRegistry.LOGGER.at(Level.WARNING).log("%s.getQuery() is using ReadWriteArchetypeEntityQuery with the same `Read` and `Modified` Archetype! This can be simplified by using the Archetype directly as the EntityQuery.", systemClass.getName());
                }
            }
        }
        long lock = this.dataLock.writeLock();
        try {
            if (!bypassClassCheck) {
                if (this.systemClasses.containsKey(systemClass)) {
                    throw new IllegalArgumentException("System of type " + systemClass.getName() + " is already registered!");
                }
            }
            else {
                this.systemBypassClassCheck.put(systemClass, true);
            }
            if (ArrayUtil.indexOf(this.systems, system) != -1) {
                throw new IllegalArgumentException("System is already registered!");
            }
            for (final Dependency<ECS_TYPE> dependency : system.getDependencies()) {
                dependency.validate(this);
            }
            this.registerSystem0(system);
            final List<DataChange> changes = new ObjectArrayList<DataChange>();
            changes.add(new SystemChange<Object>(ChangeType.REGISTERED, (ISystem<Object>)system));
            if (system instanceof final System system2) {
                final System<ECS_TYPE> theSystem = system2;
                for (final ComponentRegistration<ECS_TYPE, ?> componentRegistration : theSystem.getComponentRegistrations()) {
                    final ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = this.registerComponent0(componentRegistration);
                    changes.add(new ComponentChange<Object, Object>(ChangeType.REGISTERED, (ComponentType<Object, Object>)componentType));
                }
                for (final ResourceRegistration<ECS_TYPE, ?> resourceRegistration : theSystem.getResourceRegistrations()) {
                    final ResourceType<ECS_TYPE, ? extends Resource<ECS_TYPE>> resourceType = this.registerResource0(resourceRegistration);
                    changes.add(new ResourceChange<Object, Object>(ChangeType.REGISTERED, (ResourceType<Object, Object>)resourceType));
                }
            }
            if (system instanceof final EntityEventSystem entityEventSystem) {
                final EntityEventSystem<ECS_TYPE, ?> eventSystem = entityEventSystem;
                if (!this.entityEventTypeClassToIndex.containsKey(eventSystem.getEventType())) {
                    final EntityEventType<ECS_TYPE, ?> eventType = this.registerEntityEventType0(eventSystem.getEventType());
                    changes.add(new SystemTypeChange<Object, Object>(ChangeType.REGISTERED, (SystemType<Object, Object>)eventType));
                }
            }
            if (system instanceof final WorldEventSystem worldEventSystem) {
                final WorldEventSystem<ECS_TYPE, ?> eventSystem2 = worldEventSystem;
                if (!this.worldEventTypeClassToIndex.containsKey(eventSystem2.getEventType())) {
                    final WorldEventType<ECS_TYPE, ?> eventType2 = this.registerWorldEventType0(eventSystem2.getEventType());
                    changes.add(new SystemTypeChange<Object, Object>(ChangeType.REGISTERED, (SystemType<Object, Object>)eventType2));
                }
            }
            lock = this.dataLock.tryConvertToReadLock(lock);
            this.updateData0((DataChange[])changes.toArray(DataChange[]::new));
        }
        finally {
            this.dataLock.unlock(lock);
        }
    }
    
    public void unregisterSystem(@Nonnull final Class<? extends ISystem<ECS_TYPE>> systemClass) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        long lock = this.dataLock.writeLock();
        try {
            final int systemIndex = this.systemClasses.getInt(systemClass);
            final ISystem<ECS_TYPE> system = this.systems[systemIndex];
            if (system == null) {
                return;
            }
            if (system instanceof final QuerySystem querySystem) {
                final QuerySystem<ECS_TYPE> archetypeSystem = querySystem;
                final Query<ECS_TYPE> query = archetypeSystem.getQuery();
                query.validateRegistry(this);
                query.validate();
            }
            final int unsortedSystemIndex = ArrayUtil.indexOf(this.systems, system);
            if (unsortedSystemIndex == -1) {
                throw new IllegalArgumentException("System is not registered!");
            }
            this.unregisterSystem0(unsortedSystemIndex, system);
            final List<DataChange> changes = new ObjectArrayList<DataChange>();
            changes.add(new SystemChange<Object>(ChangeType.UNREGISTERED, (ISystem<Object>)system));
            if (system instanceof final System system2) {
                final System<ECS_TYPE> theSystem = system2;
                for (final ComponentRegistration<ECS_TYPE, ?> systemComponent : theSystem.getComponentRegistrations()) {
                    this.unregisterComponent0(systemComponent.componentType());
                    changes.add(new ComponentChange<Object, Object>(ChangeType.UNREGISTERED, (ComponentType<Object, Object>)systemComponent.componentType()));
                }
                for (final ResourceRegistration<ECS_TYPE, ?> systemResource : theSystem.getResourceRegistrations()) {
                    this.unregisterResource0(systemResource.resourceType());
                    changes.add(new ResourceChange<Object, Object>(ChangeType.UNREGISTERED, (ResourceType<Object, Object>)systemResource.resourceType()));
                }
            }
            lock = this.dataLock.tryConvertToReadLock(lock);
            this.updateData0((DataChange[])changes.toArray(DataChange[]::new));
        }
        finally {
            this.dataLock.unlock(lock);
        }
    }
    
    @Nonnull
    @Override
    public ResourceType<ECS_TYPE, SpatialResource<Ref<ECS_TYPE>, ECS_TYPE>> registerSpatialResource(@Nonnull final Supplier<SpatialStructure<Ref<ECS_TYPE>>> supplier) {
        return this.registerResource((Class<? super SpatialResource<Ref<ECS_TYPE>, ECS_TYPE>>)SpatialResource.class, () -> new SpatialResource(supplier.get()));
    }
    
    @Nonnull
    public Store<ECS_TYPE> addStore(@Nonnull final ECS_TYPE externalData, @Nonnull final IResourceStorage resourceStorage) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        return this.addStore0(externalData, resourceStorage, _store -> {});
    }
    
    @Nonnull
    public Store<ECS_TYPE> addStore(@Nonnull final ECS_TYPE externalData, @Nonnull final IResourceStorage resourceStorage, final Consumer<Store<ECS_TYPE>> consumer) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        return this.addStore0(externalData, resourceStorage, consumer);
    }
    
    public void removeStore(@Nonnull final Store<ECS_TYPE> store) {
        if (this.shutdown) {
            throw new IllegalStateException("Registry has been shutdown");
        }
        if (store.isShutdown()) {
            throw new IllegalStateException("Store is already shutdown!");
        }
        this.removeStore0(store);
    }
    
    @Nonnull
    public Holder<ECS_TYPE> newHolder() {
        final Holder<ECS_TYPE> holder = new Holder<ECS_TYPE>(this);
        this.holders.add(new WeakReference<Holder<ECS_TYPE>>(holder, this.holderReferenceQueue));
        return holder;
    }
    
    @Nonnull
    public Holder<ECS_TYPE> newHolder(@Nonnull final Archetype<ECS_TYPE> archetype, @Nonnull final Component<ECS_TYPE>[] components) {
        final Holder<ECS_TYPE> holder = new Holder<ECS_TYPE>(this, archetype, components);
        this.holders.add(new WeakReference<Holder<ECS_TYPE>>(holder, this.holderReferenceQueue));
        return holder;
    }
    
    @Nonnull
    protected Holder<ECS_TYPE> _internal_newEntityHolder() {
        return new Holder<ECS_TYPE>();
    }
    
    protected Data<ECS_TYPE> _internal_getData() {
        return this.data;
    }
    
    public Data<ECS_TYPE> getData() {
        this.assertInStoreThread();
        return this.data;
    }
    
    @Nonnull
    public BuilderCodec<Holder<ECS_TYPE>> getEntityCodec() {
        return this.data.getEntityCodec();
    }
    
    public void assertInStoreThread() {
        final long lock = this.storeLock.readLock();
        try {
            for (int i = 0; i < this.storeSize; ++i) {
                if (this.stores[i].isInThread()) {
                    return;
                }
            }
            throw new AssertionError((Object)"Data can only be accessed from a store thread!");
        }
        finally {
            this.storeLock.unlockRead(lock);
        }
    }
    
    @Nullable
    public Holder<ECS_TYPE> deserialize(@Nonnull final BsonDocument entityDocument) {
        final Optional<Integer> version = ComponentRegistry.VERSION.get(entityDocument);
        if (version.isPresent()) {
            return this.deserialize(entityDocument, version.get());
        }
        final ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get();
        final Holder<ECS_TYPE> holder = this.data.getEntityCodec().decode(entityDocument, extraInfo);
        extraInfo.getValidationResults().logOrThrowValidatorExceptions(ComponentRegistry.LOGGER);
        return holder;
    }
    
    @Nullable
    @Deprecated
    public Holder<ECS_TYPE> deserialize(@Nonnull final BsonDocument entityDocument, final int version) {
        final ExtraInfo extraInfo = new ExtraInfo(version);
        final Holder<ECS_TYPE> holder = this.data.getEntityCodec().decode(entityDocument, extraInfo);
        extraInfo.getValidationResults().logOrThrowValidatorExceptions(ComponentRegistry.LOGGER);
        return holder;
    }
    
    public BsonDocument serialize(@Nonnull final Holder<ECS_TYPE> holder) {
        final ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get();
        final BsonDocument document = this.data.getEntityCodec().encode(holder, extraInfo).asDocument();
        extraInfo.getValidationResults().logOrThrowValidatorExceptions(ComponentRegistry.LOGGER);
        return document;
    }
    
    public boolean hasSystem(@Nonnull final ISystem<ECS_TYPE> system) {
        return ArrayUtil.indexOf(this.systems, system, 0, this.systemSize) != -1;
    }
    
    public <T extends ISystem<ECS_TYPE>> boolean hasSystemClass(@Nonnull final Class<T> systemClass) {
        return this.systemClasses.containsKey(systemClass);
    }
    
    public <T extends ISystem<ECS_TYPE>> boolean hasSystemType(@Nonnull final SystemType<ECS_TYPE, T> systemType) {
        return this.systemTypeClassToIndex.containsKey(systemType.getTypeClass());
    }
    
    public boolean hasSystemGroup(@Nonnull final SystemGroup<ECS_TYPE> group) {
        return ArrayUtil.indexOf(this.systemGroups, group) != -1;
    }
    
    @Nonnull
    private <T extends Component<ECS_TYPE>> ComponentType<ECS_TYPE, T> registerComponent0(@Nonnull final ComponentRegistration<ECS_TYPE, T> registration) {
        return this.registerComponent0(registration.typeClass(), registration.id(), registration.codec(), registration.supplier(), registration.componentType());
    }
    
    @Nonnull
    private <T extends Component<ECS_TYPE>> ComponentType<ECS_TYPE, T> registerComponent0(@Nonnull final Class<? super T> tClass, @Nullable final String id, @Nullable final BuilderCodec<T> codec, @Nonnull final Supplier<T> supplier, @Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        if (id != null && this.componentIdToIndex.containsKey(id)) {
            throw new IllegalArgumentException("id '" + id + "' already exists!");
        }
        int index;
        if (this.componentIndexReuse.isEmpty()) {
            index = this.componentSize++;
        }
        else {
            index = this.componentIndexReuse.nextSetBit(0);
            this.componentIndexReuse.clear(index);
        }
        if (this.componentIds.length <= index) {
            final int newLength = ArrayUtil.grow(index);
            this.componentIds = Arrays.copyOf(this.componentIds, newLength);
            this.componentCodecs = Arrays.copyOf(this.componentCodecs, newLength);
            this.componentSuppliers = Arrays.copyOf(this.componentSuppliers, newLength);
            this.componentTypes = Arrays.copyOf(this.componentTypes, newLength);
        }
        componentType.init(this, tClass, index);
        this.componentIdToIndex.put(id, index);
        this.componentIds[index] = id;
        this.componentCodecs[index] = codec;
        this.componentSuppliers[index] = supplier;
        return (ComponentType<ECS_TYPE, T>)(this.componentTypes[index] = componentType);
    }
    
    private <T extends Component<ECS_TYPE>> void unregisterComponent0(@Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        final int componentIndex = componentType.getIndex();
        if (componentIndex == this.componentSize - 1) {
            final int highestUsedIndex = this.componentIndexReuse.previousClearBit(componentIndex - 1);
            this.componentSize = highestUsedIndex + 1;
            this.componentIndexReuse.clear(this.componentSize, componentIndex);
        }
        else {
            this.componentIndexReuse.set(componentIndex);
        }
        this.componentIdToIndex.removeInt(this.componentIds[componentIndex]);
        this.componentIds[componentIndex] = null;
        this.componentCodecs[componentIndex] = null;
        this.componentSuppliers[componentIndex] = null;
        this.componentTypes[componentIndex] = null;
    }
    
    @Nonnull
    private <T extends Resource<ECS_TYPE>> ResourceType<ECS_TYPE, T> registerResource0(@Nonnull final ResourceRegistration<ECS_TYPE, T> registration) {
        return this.registerResource0(registration.typeClass(), registration.id(), registration.codec(), registration.supplier(), registration.resourceType());
    }
    
    @Nonnull
    private <T extends Resource<ECS_TYPE>> ResourceType<ECS_TYPE, T> registerResource0(@Nonnull final Class<? super T> tClass, @Nullable final String id, @Nullable final BuilderCodec<T> codec, @Nonnull final Supplier<T> supplier, @Nonnull final ResourceType<ECS_TYPE, T> resourceType) {
        if (id != null && this.resourceIdToIndex.containsKey(id)) {
            throw new IllegalArgumentException("id '" + id + "' already exists!");
        }
        int index;
        if (this.resourceIndexReuse.isEmpty()) {
            index = this.resourceSize++;
        }
        else {
            index = this.resourceIndexReuse.nextSetBit(0);
            this.resourceIndexReuse.clear(index);
        }
        if (this.resourceIds.length <= index) {
            final int newLength = ArrayUtil.grow(index);
            this.resourceIds = Arrays.copyOf(this.resourceIds, newLength);
            this.resourceCodecs = Arrays.copyOf(this.resourceCodecs, newLength);
            this.resourceSuppliers = Arrays.copyOf(this.resourceSuppliers, newLength);
            this.resourceTypes = Arrays.copyOf(this.resourceTypes, newLength);
        }
        resourceType.init(this, tClass, index);
        this.resourceIdToIndex.put(id, index);
        this.resourceIds[index] = id;
        this.resourceCodecs[index] = codec;
        this.resourceSuppliers[index] = supplier;
        return (ResourceType<ECS_TYPE, T>)(this.resourceTypes[index] = resourceType);
    }
    
    private <T extends Resource<ECS_TYPE>> void unregisterResource0(@Nonnull final ResourceType<ECS_TYPE, T> resourceType) {
        final int resourceIndex = resourceType.getIndex();
        if (resourceIndex == this.resourceSize - 1) {
            final int highestUsedIndex = this.resourceIndexReuse.previousClearBit(resourceIndex - 1);
            this.resourceSize = highestUsedIndex + 1;
            this.resourceIndexReuse.clear(this.resourceSize, resourceIndex);
        }
        else {
            this.resourceIndexReuse.set(resourceIndex);
        }
        this.resourceIdToIndex.removeInt(this.resourceIds[resourceIndex]);
        this.resourceIds[resourceIndex] = null;
        this.resourceCodecs[resourceIndex] = null;
        this.resourceSuppliers[resourceIndex] = null;
        this.resourceTypes[resourceIndex] = null;
    }
    
    @Nonnull
    private <T extends ISystem<ECS_TYPE>> SystemType<ECS_TYPE, T> registerSystemType0(@Nonnull final Class<? super T> systemTypeClass) {
        if (this.systemTypeClassToIndex.containsKey(systemTypeClass)) {
            throw new IllegalArgumentException("system type '" + String.valueOf(systemTypeClass) + "' already exists!");
        }
        int systemTypeIndex;
        if (this.systemTypeIndexReuse.isEmpty()) {
            systemTypeIndex = this.systemTypeSize++;
        }
        else {
            systemTypeIndex = this.systemTypeIndexReuse.nextSetBit(0);
            this.systemTypeIndexReuse.clear(systemTypeIndex);
        }
        if (this.systemTypes.length <= systemTypeIndex) {
            this.systemTypes = Arrays.copyOf(this.systemTypes, ArrayUtil.grow(systemTypeIndex));
            this.systemTypeToSystemIndex = Arrays.copyOf(this.systemTypeToSystemIndex, ArrayUtil.grow(systemTypeIndex));
        }
        final Class<T> tClass = (Class<T>)systemTypeClass;
        final SystemType<ECS_TYPE, T> systemType = new SystemType<ECS_TYPE, T>(this, tClass, systemTypeIndex);
        this.systemTypeClassToIndex.put(tClass, systemTypeIndex);
        return (SystemType<ECS_TYPE, T>)(this.systemTypes[systemTypeIndex] = systemType);
    }
    
    private <T extends ISystem<ECS_TYPE>> void unregisterSystemType0(@Nonnull final SystemType<ECS_TYPE, T> systemType) {
        final int systemTypeIndex = systemType.getIndex();
        if (systemTypeIndex == this.systemTypeSize - 1) {
            final int highestUsedIndex = this.systemTypeIndexReuse.previousClearBit(systemTypeIndex - 1);
            this.systemTypeSize = highestUsedIndex + 1;
            this.systemTypeIndexReuse.clear(this.systemTypeSize, systemTypeIndex);
        }
        else {
            this.systemTypeIndexReuse.set(systemTypeIndex);
        }
        this.systemTypeClassToIndex.removeInt(systemType.getTypeClass());
        this.systemTypes[systemTypeIndex] = null;
        this.systemTypeToSystemIndex[systemTypeIndex].clear();
    }
    
    @Nonnull
    private <T extends EcsEvent> EntityEventType<ECS_TYPE, T> registerEntityEventType0(@Nonnull final Class<? super T> eventTypeClass) {
        if (this.entityEventTypeClassToIndex.containsKey(eventTypeClass)) {
            throw new IllegalArgumentException("event type '" + String.valueOf(eventTypeClass) + "' already exists!");
        }
        int systemTypeIndex;
        if (this.systemTypeIndexReuse.isEmpty()) {
            systemTypeIndex = this.systemTypeSize++;
        }
        else {
            systemTypeIndex = this.systemTypeIndexReuse.nextSetBit(0);
            this.systemTypeIndexReuse.clear(systemTypeIndex);
        }
        if (this.systemTypes.length <= systemTypeIndex) {
            this.systemTypes = Arrays.copyOf(this.systemTypes, ArrayUtil.grow(systemTypeIndex));
            this.systemTypeToSystemIndex = Arrays.copyOf(this.systemTypeToSystemIndex, ArrayUtil.grow(systemTypeIndex));
        }
        final Class<T> eClass = (Class<T>)eventTypeClass;
        final EntityEventType<ECS_TYPE, T> systemType = new EntityEventType<ECS_TYPE, T>(this, EntityEventSystem.class, eClass, systemTypeIndex);
        this.entityEventTypeClassToIndex.put(eClass, systemTypeIndex);
        return (EntityEventType<ECS_TYPE, T>)(this.systemTypes[systemTypeIndex] = (SystemType<ECS_TYPE, ? extends ISystem<ECS_TYPE>>)systemType);
    }
    
    private <T extends EcsEvent> void unregisterEntityEventType0(@Nonnull final EntityEventType<ECS_TYPE, T> eventType) {
        final int systemTypeIndex = eventType.getIndex();
        if (systemTypeIndex == this.systemTypeSize - 1) {
            final int highestUsedIndex = this.systemTypeIndexReuse.previousClearBit(systemTypeIndex - 1);
            this.systemTypeSize = highestUsedIndex + 1;
            this.systemTypeIndexReuse.clear(this.systemTypeSize, systemTypeIndex);
        }
        else {
            this.systemTypeIndexReuse.set(systemTypeIndex);
        }
        this.entityEventTypeClassToIndex.removeInt(eventType.getEventClass());
        this.systemTypes[systemTypeIndex] = null;
        this.systemTypeToSystemIndex[systemTypeIndex].clear();
    }
    
    @Nullable
    public <T extends EcsEvent> EntityEventType<ECS_TYPE, T> getEntityEventTypeForClass(final Class<T> eClass) {
        final int index = this.entityEventTypeClassToIndex.getInt(eClass);
        if (index == Integer.MIN_VALUE) {
            return null;
        }
        return (EntityEventType)this.systemTypes[index];
    }
    
    @Nonnull
    private <T extends EcsEvent> WorldEventType<ECS_TYPE, T> registerWorldEventType0(@Nonnull final Class<? super T> eventTypeClass) {
        if (this.worldEventTypeClassToIndex.containsKey(eventTypeClass)) {
            throw new IllegalArgumentException("event type '" + String.valueOf(eventTypeClass) + "' already exists!");
        }
        int systemTypeIndex;
        if (this.systemTypeIndexReuse.isEmpty()) {
            systemTypeIndex = this.systemTypeSize++;
        }
        else {
            systemTypeIndex = this.systemTypeIndexReuse.nextSetBit(0);
            this.systemTypeIndexReuse.clear(systemTypeIndex);
        }
        if (this.systemTypes.length <= systemTypeIndex) {
            this.systemTypes = Arrays.copyOf(this.systemTypes, ArrayUtil.grow(systemTypeIndex));
            this.systemTypeToSystemIndex = Arrays.copyOf(this.systemTypeToSystemIndex, ArrayUtil.grow(systemTypeIndex));
        }
        final Class<T> eClass = (Class<T>)eventTypeClass;
        final WorldEventType<ECS_TYPE, T> systemType = new WorldEventType<ECS_TYPE, T>(this, WorldEventSystem.class, eClass, systemTypeIndex);
        this.worldEventTypeClassToIndex.put(eClass, systemTypeIndex);
        return (WorldEventType<ECS_TYPE, T>)(this.systemTypes[systemTypeIndex] = (SystemType<ECS_TYPE, ? extends ISystem<ECS_TYPE>>)systemType);
    }
    
    private <T extends EcsEvent> void unregisterWorldEventType0(@Nonnull final WorldEventType<ECS_TYPE, T> eventType) {
        final int systemTypeIndex = eventType.getIndex();
        if (systemTypeIndex == this.systemTypeSize - 1) {
            final int highestUsedIndex = this.systemTypeIndexReuse.previousClearBit(systemTypeIndex - 1);
            this.systemTypeSize = highestUsedIndex + 1;
            this.systemTypeIndexReuse.clear(this.systemTypeSize, systemTypeIndex);
        }
        else {
            this.systemTypeIndexReuse.set(systemTypeIndex);
        }
        this.worldEventTypeClassToIndex.removeInt(eventType.getEventClass());
        this.systemTypes[systemTypeIndex] = null;
        this.systemTypeToSystemIndex[systemTypeIndex].clear();
    }
    
    @Nullable
    public <T extends EcsEvent> WorldEventType<ECS_TYPE, T> getWorldEventTypeForClass(final Class<T> eClass) {
        final int index = this.worldEventTypeClassToIndex.getInt(eClass);
        if (index == Integer.MIN_VALUE) {
            return null;
        }
        return (WorldEventType)this.systemTypes[index];
    }
    
    @Nonnull
    private SystemGroup<ECS_TYPE> registerSystemGroup0(@Nonnull final Set<Dependency<ECS_TYPE>> dependencies) {
        int systemGroupIndex;
        if (this.systemGroupIndexReuse.isEmpty()) {
            systemGroupIndex = this.systemGroupSize++;
        }
        else {
            systemGroupIndex = this.systemGroupIndexReuse.nextSetBit(0);
            this.systemGroupIndexReuse.clear(systemGroupIndex);
        }
        if (this.systemGroups.length <= systemGroupIndex) {
            this.systemGroups = Arrays.copyOf(this.systemGroups, ArrayUtil.grow(systemGroupIndex));
        }
        final SystemGroup<ECS_TYPE> systemGroup = new SystemGroup<ECS_TYPE>(this, systemGroupIndex, dependencies);
        return this.systemGroups[systemGroupIndex] = systemGroup;
    }
    
    private void unregisterSystemGroup0(@Nonnull final SystemGroup<ECS_TYPE> systemType) {
        final int systemGroupIndex = systemType.getIndex();
        if (systemGroupIndex == this.systemGroupSize - 1) {
            final int highestUsedIndex = this.systemGroupIndexReuse.previousClearBit(systemGroupIndex - 1);
            this.systemGroupSize = highestUsedIndex + 1;
            this.systemGroupIndexReuse.clear(this.systemGroupSize, systemGroupIndex);
        }
        else {
            this.systemGroupIndexReuse.set(systemGroupIndex);
        }
        this.systemGroups[systemGroupIndex] = null;
    }
    
    private void registerSystem0(@Nonnull final ISystem<ECS_TYPE> system) {
        final int systemIndex = this.systemSize++;
        if (this.systems.length <= systemIndex) {
            this.systems = Arrays.copyOf(this.systems, ArrayUtil.grow(systemIndex));
        }
        this.systems[systemIndex] = system;
        this.systemClasses.put(system.getClass(), systemIndex);
        system.onSystemRegistered();
    }
    
    private void unregisterSystem0(final int systemIndex, @Nonnull final ISystem<ECS_TYPE> system) {
        final int lastIndex = this.systemSize - 1;
        if (systemIndex != lastIndex) {
            final ISystem<ECS_TYPE> lastSystem = this.systems[lastIndex];
            this.systems[systemIndex] = lastSystem;
            this.systemClasses.put(lastSystem.getClass(), systemIndex);
        }
        this.systems[lastIndex] = null;
        this.systemSize = lastIndex;
        final Class<? extends ISystem> systemClass = system.getClass();
        final boolean bypassClassCheck = this.systemBypassClassCheck.getBoolean(systemClass);
        if (!bypassClassCheck && !this.systemClasses.remove(systemClass, systemIndex)) {
            throw new IllegalArgumentException("Failed to remove system " + systemClass.getName() + ", " + systemIndex);
        }
        system.onSystemUnregistered();
    }
    
    @Nonnull
    private Store<ECS_TYPE> addStore0(@Nonnull final ECS_TYPE externalData, @Nonnull final IResourceStorage resourceStorage, final Consumer<Store<ECS_TYPE>> consumer) {
        final long lock = this.storeLock.writeLock();
        try {
            final int storeIndex = this.storeSize++;
            if (this.stores.length <= storeIndex) {
                this.stores = Arrays.copyOf(this.stores, ArrayUtil.grow(storeIndex));
            }
            final Store<ECS_TYPE> store = new Store<ECS_TYPE>(this, storeIndex, externalData, resourceStorage);
            consumer.accept(this.stores[storeIndex] = store);
            store.onAdd(this.data);
            return store;
        }
        finally {
            this.storeLock.unlockWrite(lock);
        }
    }
    
    private void removeStore0(@Nonnull final Store<ECS_TYPE> store) {
        store.shutdown0(this.data);
        long lock;
        do {
            Thread.onSpinWait();
            this.doDataUpdate();
            lock = this.storeLock.tryWriteLock();
        } while (!this.storeLock.validate(lock));
        try {
            final int storeIndex = store.storeIndex;
            final int lastIndex = this.storeSize - 1;
            if (storeIndex != lastIndex) {
                final Store<ECS_TYPE> lastStore = this.stores[lastIndex];
                lastStore.storeIndex = storeIndex;
                this.stores[storeIndex] = lastStore;
            }
            this.stores[lastIndex] = null;
            this.storeSize = lastIndex;
        }
        finally {
            this.storeLock.unlockWrite(lock);
        }
    }
    
    Data<ECS_TYPE> doDataUpdate() {
        return this.data;
    }
    
    private void updateData0(@Nonnull final DataChange... dataChanges) {
        boolean systemChanged = false;
        boolean systemTypeChanged = false;
        for (final DataChange dataChange : dataChanges) {
            if (dataChange instanceof SystemChange) {
                systemChanged = true;
            }
            if (dataChange instanceof SystemTypeChange) {
                systemTypeChanged = true;
            }
        }
        if (systemChanged) {
            if (this.sortedSystems.length < this.systems.length) {
                this.sortedSystems = Arrays.copyOf(this.systems, this.systems.length);
            }
            else {
                java.lang.System.arraycopy(this.systems, 0, this.sortedSystems, 0, this.systems.length);
            }
            ISystem.calculateOrder(this, this.sortedSystems, this.systemSize);
        }
        if (systemChanged || systemTypeChanged) {
            for (int systemTypeIndex = 0; systemTypeIndex < this.systemTypeSize; ++systemTypeIndex) {
                final SystemType<ECS_TYPE, ? extends ISystem<ECS_TYPE>> systemType = this.systemTypes[systemTypeIndex];
                if (systemType != null) {
                    final BitSet[] systemTypeToSystemIndex = this.systemTypeToSystemIndex;
                    final int n = systemTypeIndex;
                    final BitSet set = new BitSet();
                    systemTypeToSystemIndex[n] = set;
                    final BitSet bitSet = set;
                    for (int systemIndex = 0; systemIndex < this.systemSize; ++systemIndex) {
                        if (systemType.isType(this.sortedSystems[systemIndex])) {
                            bitSet.set(systemIndex);
                        }
                    }
                }
            }
        }
        final long lock = this.storeLock.readLock();
        this.dataUpdateLock.writeLock().lock();
        try {
            final Data<ECS_TYPE> oldData = this.data;
            this.data = new Data<ECS_TYPE>(oldData.getVersion() + 1, this, dataChanges);
            for (int i = 0; i < this.storeSize; ++i) {
                this.stores[i].updateData(oldData, this.data);
            }
            for (final Reference<Holder<ECS_TYPE>> holderReference : this.holders) {
                final Holder<ECS_TYPE> holder = holderReference.get();
                if (holder != null) {
                    holder.updateData(oldData, this.data);
                }
            }
        }
        finally {
            this.dataUpdateLock.writeLock().unlock();
            this.storeLock.unlockRead(lock);
        }
    }
    
    @Nonnull
    @Override
    public String toString() {
        return "ComponentRegistry{super()=" + String.valueOf(this.getClass()) + "@" + this.hashCode() + ", shutdown=" + this.shutdown + ", dataLock=" + String.valueOf(this.dataLock) + ", idToIndex=" + String.valueOf(this.componentIdToIndex) + ", componentIndexReuse=" + String.valueOf(this.componentIndexReuse) + ", componentSize=" + this.componentSize + ", componentIds=" + Arrays.toString(this.componentIds) + ", componentCodecs=" + Arrays.toString(this.componentCodecs) + ", componentSuppliers=" + Arrays.toString(this.componentSuppliers) + ", componentTypes=" + Arrays.toString(this.componentTypes) + ", resourceIndexReuse=" + String.valueOf(this.resourceIndexReuse) + ", resourceSize=" + this.resourceSize + ", resourceIds=" + Arrays.toString(this.resourceIds) + ", resourceCodecs=" + Arrays.toString(this.resourceCodecs) + ", resourceSuppliers=" + Arrays.toString(this.resourceSuppliers) + ", resourceTypes=" + Arrays.toString(this.resourceTypes) + ", systemSize=" + this.systemSize + ", systems=" + Arrays.toString(this.systems) + ", sortedSystems=" + Arrays.toString(this.sortedSystems) + ", storeLock=" + String.valueOf(this.storeLock) + ", storeSize=" + this.storeSize + ", stores=" + Arrays.toString(this.stores) + ", data=" + String.valueOf(this.data);
    }
    
    public <T extends Component<ECS_TYPE>> T createComponent(@Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        final long lock = this.dataLock.readLock();
        try {
            return this.data.createComponent(componentType);
        }
        finally {
            this.dataLock.unlockRead(lock);
        }
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
        VERSION = new KeyedCodec<Integer>("Version", Codec.INTEGER);
        REFERENCE_THREAD_COUNTER = new AtomicInteger();
    }
    
    public static class Data<ECS_TYPE>
    {
        private final int version;
        @Nonnull
        private final ComponentRegistry<ECS_TYPE> registry;
        private final Object2IntMap<String> componentIdToIndex;
        private final int componentSize;
        @Nonnull
        private final String[] componentIds;
        @Nonnull
        private final BuilderCodec<? extends Component<ECS_TYPE>>[] componentCodecs;
        @Nonnull
        private final Supplier<? extends Component<ECS_TYPE>>[] componentSuppliers;
        @Nonnull
        private final ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>[] componentTypes;
        private final Object2IntMap<String> resourceIdToIndex;
        private final int resourceSize;
        @Nonnull
        private final String[] resourceIds;
        @Nonnull
        private final BuilderCodec<? extends Resource<ECS_TYPE>>[] resourceCodecs;
        @Nonnull
        private final Supplier<? extends Resource<ECS_TYPE>>[] resourceSuppliers;
        @Nonnull
        private final ResourceType<ECS_TYPE, ? extends Resource<ECS_TYPE>>[] resourceTypes;
        private final Object2IntMap<Class<? extends ISystem<ECS_TYPE>>> systemTypeClassToIndex;
        private final int systemTypeSize;
        @Nonnull
        private final SystemType<ECS_TYPE, ? extends ISystem<ECS_TYPE>>[] systemTypes;
        @Nonnull
        private final BitSet[] systemTypeToSystemIndex;
        private final int systemSize;
        @Nonnull
        private final ISystem<ECS_TYPE>[] sortedSystems;
        @Nonnull
        private final Map<String, Codec<Component<ECS_TYPE>>> codecMap;
        @Nonnull
        private final BuilderCodec<Holder<ECS_TYPE>> entityCodec;
        @Nullable
        private final DataChange[] dataChanges;
        
        private Data(@Nonnull final ComponentRegistry<ECS_TYPE> registry) {
            this.version = 0;
            this.registry = registry;
            this.componentIdToIndex = Object2IntMaps.emptyMap();
            this.componentSize = 0;
            this.componentIds = ArrayUtil.EMPTY_STRING_ARRAY;
            this.componentCodecs = (BuilderCodec<? extends Component<ECS_TYPE>>[])BuilderCodec.EMPTY_ARRAY;
            this.componentSuppliers = ArrayUtil.emptySupplierArray();
            this.componentTypes = ComponentType.EMPTY_ARRAY;
            this.resourceIdToIndex = Object2IntMaps.emptyMap();
            this.resourceSize = 0;
            this.resourceIds = ArrayUtil.EMPTY_STRING_ARRAY;
            this.resourceCodecs = (BuilderCodec<? extends Resource<ECS_TYPE>>[])BuilderCodec.EMPTY_ARRAY;
            this.resourceSuppliers = ArrayUtil.emptySupplierArray();
            this.resourceTypes = ResourceType.EMPTY_ARRAY;
            this.systemTypeClassToIndex = Object2IntMaps.emptyMap();
            this.systemTypeSize = 0;
            this.systemTypes = SystemType.EMPTY_ARRAY;
            this.systemTypeToSystemIndex = ArrayUtil.EMPTY_BITSET_ARRAY;
            this.systemSize = 0;
            this.sortedSystems = ISystem.EMPTY_ARRAY;
            this.codecMap = Collections.emptyMap();
            this.entityCodec = this.createCodec();
            this.dataChanges = null;
        }
        
        private Data(final int version, @Nonnull final ComponentRegistry<ECS_TYPE> registry, final DataChange... dataChanges) {
            this.version = version;
            this.registry = registry;
            (this.componentIdToIndex = new Object2IntOpenHashMap<String>(registry.componentIdToIndex)).defaultReturnValue(Integer.MIN_VALUE);
            this.componentSize = registry.componentSize;
            this.componentIds = Arrays.copyOf(registry.componentIds, this.componentSize);
            this.componentCodecs = Arrays.copyOf(registry.componentCodecs, this.componentSize);
            this.componentSuppliers = Arrays.copyOf(registry.componentSuppliers, this.componentSize);
            this.componentTypes = Arrays.copyOf(registry.componentTypes, this.componentSize);
            (this.resourceIdToIndex = new Object2IntOpenHashMap<String>(registry.resourceIdToIndex)).defaultReturnValue(Integer.MIN_VALUE);
            this.resourceSize = registry.resourceSize;
            this.resourceIds = Arrays.copyOf(registry.resourceIds, this.resourceSize);
            this.resourceCodecs = Arrays.copyOf(registry.resourceCodecs, this.resourceSize);
            this.resourceSuppliers = Arrays.copyOf(registry.resourceSuppliers, this.resourceSize);
            this.resourceTypes = Arrays.copyOf(registry.resourceTypes, this.resourceSize);
            (this.systemTypeClassToIndex = new Object2IntOpenHashMap<Class<? extends ISystem<ECS_TYPE>>>(registry.systemTypeClassToIndex)).defaultReturnValue(Integer.MIN_VALUE);
            this.systemTypeSize = registry.systemTypeSize;
            this.systemTypes = Arrays.copyOf(registry.systemTypes, this.systemTypeSize);
            this.systemTypeToSystemIndex = Arrays.copyOf(registry.systemTypeToSystemIndex, this.systemTypeSize);
            this.systemSize = registry.systemSize;
            this.sortedSystems = Arrays.copyOf(registry.sortedSystems, this.systemSize);
            final Object2ObjectOpenHashMap<String, Codec<Component<ECS_TYPE>>> codecMap = new Object2ObjectOpenHashMap<String, Codec<Component<ECS_TYPE>>>(this.componentSize);
            for (int i = 0; i < this.componentSize; ++i) {
                if (this.componentCodecs[i] != null) {
                    codecMap.put(this.componentIds[i], (Codec<Component<ECS_TYPE>>)this.componentCodecs[i]);
                }
            }
            this.codecMap = codecMap;
            this.entityCodec = this.createCodec();
            this.dataChanges = dataChanges;
        }
        
        @Nonnull
        private BuilderCodec<Holder<ECS_TYPE>> createCodec() {
            // 
            // This method could not be decompiled.
            // 
            // Original Bytecode:
            // 
            //     5: astore_1        /* function */
            //     6: ldc             Lcom/hypixel/hytale/component/Holder;.class
            //     8: aload_0         /* this */
            //     9: getfield        com/hypixel/hytale/component/ComponentRegistry$Data.registry:Lcom/hypixel/hytale/component/ComponentRegistry;
            //    12: dup            
            //    13: invokestatic    java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
            //    16: pop            
            //    17: invokedynamic   BootstrapMethod #1, get:(Lcom/hypixel/hytale/component/ComponentRegistry;)Ljava/util/function/Supplier;
            //    22: invokestatic    com/hypixel/hytale/codec/builder/BuilderCodec.builder:(Ljava/lang/Class;Ljava/util/function/Supplier;)Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
            //    25: new             Lcom/hypixel/hytale/codec/KeyedCodec;
            //    28: dup            
            //    29: ldc             "Components"
            //    31: new             Lcom/hypixel/hytale/codec/lookup/MapProvidedMapCodec;
            //    34: dup            
            //    35: aload_0         /* this */
            //    36: getfield        com/hypixel/hytale/component/ComponentRegistry$Data.codecMap:Ljava/util/Map;
            //    39: aload_1         /* function */
            //    40: invokedynamic   BootstrapMethod #2, get:()Ljava/util/function/Supplier;
            //    45: iconst_0       
            //    46: invokespecial   com/hypixel/hytale/codec/lookup/MapProvidedMapCodec.<init>:(Ljava/util/Map;Ljava/util/function/Function;Ljava/util/function/Supplier;Z)V
            //    49: invokespecial   com/hypixel/hytale/codec/KeyedCodec.<init>:(Ljava/lang/String;Lcom/hypixel/hytale/codec/Codec;)V
            //    52: aload_0         /* this */
            //    53: invokedynamic   BootstrapMethod #3, accept:(Lcom/hypixel/hytale/component/ComponentRegistry$Data;)Ljava/util/function/BiConsumer;
            //    58: aload_0         /* this */
            //    59: invokedynamic   BootstrapMethod #4, apply:(Lcom/hypixel/hytale/component/ComponentRegistry$Data;)Ljava/util/function/Function;
            //    64: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.append:(Lcom/hypixel/hytale/codec/KeyedCodec;Ljava/util/function/BiConsumer;Ljava/util/function/Function;)Lcom/hypixel/hytale/codec/builder/BuilderField$FieldBuilder;
            //    67: invokevirtual   com/hypixel/hytale/codec/builder/BuilderField$FieldBuilder.add:()Lcom/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase;
            //    70: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase.build:()Lcom/hypixel/hytale/codec/builder/BuilderCodec;
            //    73: areturn        
            //    Signature:
            //  ()Lcom/hypixel/hytale/codec/builder/BuilderCodec<Lcom/hypixel/hytale/component/Holder<TECS_TYPE;>;>;
            // 
            // The error that occurred was:
            // 
            // java.lang.UnsupportedOperationException: The requested operation is not supported.
            //     at com.strobel.util.ContractUtils.unsupported(ContractUtils.java:27)
            //     at com.strobel.assembler.metadata.TypeReference.getRawType(TypeReference.java:284)
            //     at com.strobel.assembler.metadata.TypeReference.getRawType(TypeReference.java:279)
            //     at com.strobel.assembler.metadata.TypeReference.makeGenericType(TypeReference.java:154)
            //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitParameterizedType(TypeSubstitutionVisitor.java:225)
            //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitParameterizedType(TypeSubstitutionVisitor.java:25)
            //     at com.strobel.assembler.metadata.ParameterizedType.accept(ParameterizedType.java:103)
            //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visit(TypeSubstitutionVisitor.java:40)
            //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitParameterizedType(TypeSubstitutionVisitor.java:211)
            //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitParameterizedType(TypeSubstitutionVisitor.java:25)
            //     at com.strobel.assembler.metadata.ParameterizedType.accept(ParameterizedType.java:103)
            //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visit(TypeSubstitutionVisitor.java:40)
            //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitMethod(TypeSubstitutionVisitor.java:314)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2611)
            //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:782)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:778)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2483)
            //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:782)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:778)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2483)
            //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
            //     at com.strobel.decompiler.ast.TypeAnalysis.runInference(TypeAnalysis.java:684)
            //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypesForVariables(TypeAnalysis.java:593)
            //     at com.strobel.decompiler.ast.TypeAnalysis.runInference(TypeAnalysis.java:405)
            //     at com.strobel.decompiler.ast.TypeAnalysis.run(TypeAnalysis.java:95)
            //     at com.strobel.decompiler.ast.AstOptimizer.optimize(AstOptimizer.java:109)
            //     at com.strobel.decompiler.ast.AstOptimizer.optimize(AstOptimizer.java:42)
            //     at com.strobel.decompiler.languages.java.ast.AstMethodBodyBuilder.createMethodBody(AstMethodBodyBuilder.java:206)
            //     at com.strobel.decompiler.languages.java.ast.AstMethodBodyBuilder.createMethodBody(AstMethodBodyBuilder.java:93)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createMethodBody(AstBuilder.java:868)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createMethod(AstBuilder.java:761)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.addTypeMembers(AstBuilder.java:638)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createTypeCore(AstBuilder.java:605)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createTypeNoCache(AstBuilder.java:195)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.addTypeMembers(AstBuilder.java:662)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createTypeCore(AstBuilder.java:605)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createTypeNoCache(AstBuilder.java:195)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createType(AstBuilder.java:162)
            //     at com.strobel.decompiler.languages.java.ast.AstBuilder.addType(AstBuilder.java:137)
            //     at com.strobel.decompiler.languages.java.JavaLanguage.buildAst(JavaLanguage.java:71)
            //     at com.strobel.decompiler.languages.java.JavaLanguage.decompileType(JavaLanguage.java:59)
            //     at com.strobel.decompiler.DecompilerDriver.decompileType(DecompilerDriver.java:333)
            //     at com.strobel.decompiler.DecompilerDriver.decompileJar(DecompilerDriver.java:254)
            //     at com.strobel.decompiler.DecompilerDriver.main(DecompilerDriver.java:129)
            // 
            throw new IllegalStateException("An error occurred while decompiling this method.");
        }
        
        public int getVersion() {
            return this.version;
        }
        
        @Nonnull
        public ComponentRegistry<ECS_TYPE> getRegistry() {
            return this.registry;
        }
        
        @Nullable
        public ComponentType<ECS_TYPE, ?> getComponentType(final String id) {
            final int index = this.componentIdToIndex.getInt(id);
            if (index == Integer.MIN_VALUE) {
                return null;
            }
            return this.componentTypes[index];
        }
        
        public int getComponentSize() {
            return this.componentSize;
        }
        
        @Nullable
        public String getComponentId(@Nonnull final ComponentType<ECS_TYPE, ?> componentType) {
            return this.componentIds[componentType.getIndex()];
        }
        
        @Nullable
        public <T extends Component<ECS_TYPE>> Codec<T> getComponentCodec(@Nonnull final ComponentType<ECS_TYPE, T> componentType) {
            return (Codec<T>)this.componentCodecs[componentType.getIndex()];
        }
        
        public <T extends Component<ECS_TYPE>> T createComponent(@Nonnull final ComponentType<ECS_TYPE, T> componentType) {
            componentType.validateRegistry(this.registry);
            componentType.validate();
            return (T)this.componentSuppliers[componentType.getIndex()].get();
        }
        
        public ResourceType<ECS_TYPE, ?> getResourceType(final int index) {
            return this.resourceTypes[index];
        }
        
        @Nullable
        public ResourceType<ECS_TYPE, ?> getResourceType(final String id) {
            final int index = this.resourceIdToIndex.getInt(id);
            if (index == Integer.MIN_VALUE) {
                return null;
            }
            return this.resourceTypes[index];
        }
        
        public int getResourceSize() {
            return this.resourceSize;
        }
        
        @Nullable
        public String getResourceId(@Nonnull final ResourceType<ECS_TYPE, ?> resourceType) {
            return this.resourceIds[resourceType.getIndex()];
        }
        
        @Nullable
        public <T extends Resource<ECS_TYPE>> BuilderCodec<T> getResourceCodec(@Nonnull final ResourceType<ECS_TYPE, T> resourceType) {
            return (BuilderCodec<T>)this.resourceCodecs[resourceType.getIndex()];
        }
        
        public <T extends Resource<ECS_TYPE>> T createResource(@Nonnull final ResourceType<ECS_TYPE, T> resourceType) {
            resourceType.validateRegistry(this.registry);
            resourceType.validate();
            return (T)this.resourceSuppliers[resourceType.getIndex()].get();
        }
        
        public int getSystemTypeSize() {
            return this.systemTypeSize;
        }
        
        @Nullable
        public <T extends ISystem<ECS_TYPE>> SystemType<ECS_TYPE, T> getSystemType(final Class<? super T> systemTypeClass) {
            final int systemTypeClassToIndexInt = this.systemTypeClassToIndex.getInt(systemTypeClass);
            if (systemTypeClassToIndexInt == Integer.MIN_VALUE) {
                return null;
            }
            return (SystemType<ECS_TYPE, T>)this.systemTypes[systemTypeClassToIndexInt];
        }
        
        public SystemType<ECS_TYPE, ? extends ISystem<ECS_TYPE>> getSystemType(final int systemTypeIndex) {
            return this.systemTypes[systemTypeIndex];
        }
        
        public <T extends ISystem<ECS_TYPE>> BitSet getSystemIndexesForType(@Nonnull final SystemType<ECS_TYPE, T> systemType) {
            return this.systemTypeToSystemIndex[systemType.getIndex()];
        }
        
        public int getSystemSize() {
            return this.systemSize;
        }
        
        public ISystem<ECS_TYPE> getSystem(final int systemIndex) {
            return this.sortedSystems[systemIndex];
        }
        
        public <T extends ISystem<ECS_TYPE>> T getSystem(final int systemIndex, final SystemType<ECS_TYPE, T> systemType) {
            return (T)this.sortedSystems[systemIndex];
        }
        
        public int indexOf(final ISystem<ECS_TYPE> system) {
            int systemIndex = -1;
            for (int i = 0; i < this.sortedSystems.length; ++i) {
                if (this.sortedSystems[i] == system) {
                    systemIndex = i;
                    break;
                }
            }
            return systemIndex;
        }
        
        @Nonnull
        public BuilderCodec<Holder<ECS_TYPE>> getEntityCodec() {
            return this.entityCodec;
        }
        
        public int getDataChangeCount() {
            return this.dataChanges.length;
        }
        
        public DataChange getDataChange(final int index) {
            return this.dataChanges[index];
        }
        
        @Override
        public boolean equals(@Nullable final Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || this.getClass() != o.getClass()) {
                return false;
            }
            final Data<?> data = (Data<?>)o;
            return this.version == data.version;
        }
        
        @Override
        public int hashCode() {
            return this.version;
        }
        
        @Nonnull
        @Override
        public String toString() {
            return "Data{version=" + this.version + ", componentSize=" + this.componentSize + ", componentSuppliers=" + Arrays.toString(this.componentSuppliers) + ", resourceSize=" + this.resourceSize + ", resourceSuppliers=" + Arrays.toString(this.resourceSuppliers) + ", systemSize=" + this.systemSize + ", sortedSystems=" + Arrays.toString(this.sortedSystems) + ", dataChanges=" + Arrays.toString(this.dataChanges);
        }
        
        public void appendDump(@Nonnull final String prefix, @Nonnull final StringBuilder sb) {
            sb.append(prefix).append("version=").append(this.version).append("\n");
            sb.append(prefix).append("componentSize=").append(this.componentSize).append("\n");
            sb.append(prefix).append("componentSuppliers=").append("\n");
            for (int i = 0; i < this.componentSize; ++i) {
                sb.append(prefix).append("\t- ").append(i).append("\t").append(this.componentSuppliers[i]).append("\n");
            }
            sb.append(prefix).append("resourceSuppliers=").append("\n");
            for (int i = 0; i < this.resourceSize; ++i) {
                sb.append(prefix).append("\t- ").append(i).append("\t").append(this.resourceSuppliers[i]).append("\n");
            }
            sb.append(prefix).append("systemSize=").append(this.systemSize).append("\n");
            sb.append(prefix).append("sortedSystems=").append("\n");
            for (int i = 0; i < this.systemSize; ++i) {
                sb.append(prefix).append("\t- ").append(i).append("\t").append(this.sortedSystems[i]).append("\n");
            }
            sb.append(prefix).append("dataChanges=").append("\n");
            for (int i = 0; i < this.dataChanges.length; ++i) {
                sb.append(prefix).append("\t- ").append(i).append("\t").append(this.dataChanges[i]).append("\n");
            }
        }
    }
}
