package com.wildex999.tickdynamic.listinject; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Queue; import java.util.Set; import org.apache.commons.lang3.NotImplementedException; import com.wildex999.tickdynamic.TickDynamicConfig; import com.wildex999.tickdynamic.TickDynamicMod; import com.wildex999.tickdynamic.timemanager.TimeManager; import com.wildex999.tickdynamic.timemanager.TimedEntities; import com.wildex999.tickdynamic.timemanager.TimedGroup; import cpw.mods.fml.common.registry.EntityRegistry; import cpw.mods.fml.common.registry.GameData; import cpw.mods.fml.common.registry.GameRegistry; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.world.World; import net.minecraftforge.common.config.ConfigCategory; import net.minecraftforge.common.config.Property; /* * Written by: Wildex999 * * Overrides ArrayList to act as an replacement for loadedEntityList and loadedTileEntityList. */ public class ListManager implements List<EntityObject> { protected World world; protected TickDynamicMod mod; protected EntityType entityType; protected HashSet<EntityGroup> localGroups; //Groups local to the world this list is part of protected Map<Class, EntityGroup> groupMap; //Map of Class to Group protected EntityGroup ungroupedEntities; protected Queue<EntityObject> queuedEntities; //Entities awaiting grouping, ticked as part of ungroupedEntities protected List<EntityPlayer> playerEntities; //List of players that should tick every server tick protected CustomProfiler customProfiler; protected int entityCount; //Real count of entities combined in all groups protected int age; //Used to invalidate iterators if list changes public ListManager(World world, TickDynamicMod mod, EntityType type) { this.world = world; this.customProfiler = (CustomProfiler)world.theProfiler; this.mod = mod; this.entityType = type; localGroups = new HashSet<EntityGroup>(); groupMap = new HashMap<Class, EntityGroup>(); playerEntities = new ArrayList<EntityPlayer>(); queuedEntities = new ArrayDeque<EntityObject>(); entityCount = 0; age = 0; if(mod.debug) System.out.println("Initializing " + type + " list for world: " + world.provider.getDimensionName() + "(DIM" + world.provider.dimensionId + ")"); //Add groups from config loadLocalGroups(); loadGlobalGroups(); //Set default group for ungrouped if(type == EntityType.Entity) ungroupedEntities = mod.getWorldEntityGroup(world, "entity", type, false, false); else ungroupedEntities = mod.getWorldEntityGroup(world, "tileentity", type, false, false); if(ungroupedEntities == null || !localGroups.contains(ungroupedEntities) ) throw new RuntimeException("TickDynamic Assert failure: Could not find " + type + " group during world initialization!"); createGroupMap(); } //Add any local groups which are not already loaded private void loadLocalGroups() { //Add local groups from config ConfigCategory config = mod.getWorldConfigCategory(world); Iterator<ConfigCategory> localIt; for(localIt = config.getChildren().iterator(); localIt.hasNext(); ) { ConfigCategory localGroupCategory = localIt.next(); String name = localGroupCategory.getName(); EntityGroup localGroup = mod.getWorldEntityGroup(world, name, entityType, true, true); if(localGroup.getGroupType() != entityType || localGroups.contains(localGroup)) continue; if(mod.debug) System.out.println("Load local group: " + name); localGroups.add(localGroup); localGroup.list = this; } } //Add a copy of any global groups who are not already loaded private void loadGlobalGroups() { ConfigCategory config = mod.config.getCategory("groups"); Iterator<ConfigCategory> globalIt; for(globalIt = config.getChildren().iterator(); globalIt.hasNext(); ) { ConfigCategory groupCategory = globalIt.next(); String name = groupCategory.getName(); EntityGroup globalGroup = mod.getEntityGroup("groups." + name); if(globalGroup == null || globalGroup.getGroupType() != entityType) continue; //Get or create the local group as a copy of the global, but without a world config entry. //Will inherit config from the global group. EntityGroup localGroup = mod.getWorldEntityGroup(world, name, entityType, true, false); if(localGroups.contains(localGroup)) continue; //Local group already defined if(mod.debug) System.out.println("Load global group: " + name); localGroups.add(localGroup); localGroup.list = this; } } //Create new Class to Group map public void createGroupMap() { if(mod.debug) System.out.println("Creating Group map"); groupMap.clear(); //Create map of ID to group for(EntityGroup group : localGroups) { Set<Class> entries = group.getEntityEntries(); for(Class entityClass : entries) { if(mod.debugGroups) { String localPath = group.getConfigEntry(); if(localPath == null) localPath = "-"; String parentPath = "None"; if(group.base != null) parentPath = group.base.getConfigEntry(); System.out.println("Mapping: " + entityClass + " -> " + localPath + "(Global: " + parentPath + ")"); } groupMap.put(entityClass, group); } } if(mod.debug) System.out.println("Done!"); } //Re-create groups from config, and move any entities in/out due to change public void reloadGroups() { //TODO: Do partial updates each tick to not stop the world, I.e 1% of groups per tick? //Reload config, marking for removal those who no longer exists TickDynamicConfig.loadGroups(mod, "worlds.dim" + world.provider.dimensionId); //Move all EntityObjects to new list for later resorting into new groups ArrayList<EntityObject> entityList = new ArrayList<EntityObject>(entityCount); Iterator<EntityGroup> groupIterator = localGroups.iterator(); while(groupIterator.hasNext()) { EntityGroup group = groupIterator.next(); entityList.addAll(group.entities); group.clearEntities(); //Group was removed from config if(!group.valid || (group.base != null && !group.base.valid)) groupIterator.remove(); else group.readConfig(false); } //Load any new groups loadLocalGroups(); loadGlobalGroups(); //Recreate the Group Map in case the groups have changed createGroupMap(); //Re-sort entities into the new groups for(EntityObject entity : entityList) assignToGroup(entity); } //Assign the given EntityObject to an appropriate group public void assignToGroup(EntityObject object) { if(object == null) return; EntityGroup group = object.TD_entityGroup; if(group != null) group.removeEntity(object); group = groupMap.get(object.getClass()); if(group == null) { if(mod.debugGroups) System.out.println("Adding Entity: " + object.getClass() + " -> Ungrouped(" + entityType + ")"); ungroupedEntities.addEntity(object); } else { if(mod.debugGroups) System.out.println("Adding Entity: " + object.getClass() + " -> " + group.getName()); group.addEntity(object); } } public int getAge() { return age; } //Get a new iterator for the local groups public Iterator<EntityGroup> getGroupIterator() { return localGroups.iterator(); } @Override public boolean add(EntityObject element) { if(element.TD_entityGroup != null) return false; //TODO: Queue and add over time //ungroupedEntities.addEntity(element); //queuedEntities.add(element); //Add to queue for later insertion into a group assignToGroup(element); entityCount++; //age++; //We allow adding without aging, as it does not disrupt the iterators return true; } @Override public void add(int index, EntityObject element) { add(element); //We ignore index(Not used in Minecraft, and doesn't make sense for us) } @Override public boolean addAll(Collection<? extends EntityObject> c) { //TODO: Actually verify that the list did change before returning true for(EntityObject element : c) add(element); return true; } @Override public boolean addAll(int index, Collection<? extends EntityObject> c) { return addAll(c); } @Override public void clear() { for(EntityGroup group : localGroups) { group.clearEntities(); } entityCount = 0; age++; if(mod.debug) System.out.println("Cleared all loaded object of the type " + entityType + " from world: " + (world == null ? "Unknown" : world.provider.getDimensionName())); } @Override public boolean contains(Object object) { if(!(object instanceof EntityObject)) { if(mod.debug) System.err.println("Trying to remove: " + object + " but not instanceof class EntityObject"); return false; } EntityObject entityObject = (EntityObject)object; if(entityObject.TD_entityGroup == null || entityObject.TD_entityGroup.list != this) { if(mod.debug) { System.err.println("Contains check: " + object + " does not belong to list: " + this + " but instead " + (entityObject.TD_entityGroup == null ? "None" : entityObject.TD_entityGroup.list)); Thread.currentThread().dumpStack(); } return false; } return true; } @Override public boolean containsAll(Collection<?> c) { for(Object obj : c) { if(!contains(obj)) return false; } return true; } @Override public EntityObject get(int index) { if(index >= entityCount || index < 0) throw new IndexOutOfBoundsException("Tried to get index: " + index + ", but size is: " + entityCount); //Walk through groups, adding their size to index, until we reach the group with the index //Note: localGroups's order is not guaranteed to remain the same after a change. int offset = 0; for(EntityGroup group : localGroups) { if(offset + group.getEntityCount() > index) return group.entities.get(index - offset); offset += group.getEntityCount(); } throw new IndexOutOfBoundsException("Reached end of groups before finding index: " + index); } @Override public int indexOf(Object o) { if(!(o instanceof EntityObject)) return -1; EntityObject obj = (EntityObject)o; if(obj.TD_entityGroup == null || obj.TD_entityGroup.list != this) return -1; //Same strategy as get(index), jump groups, adding their size until we reach the group containing the Object int offset = 0; for(EntityGroup group : localGroups) { if(obj.TD_entityGroup == group) { int index = group.entities.indexOf(obj); if(index == -1) return -1; return offset + index; } offset += group.getEntityCount(); } return -1; } @Override public boolean isEmpty() { return entityCount == 0 ? true : false; } @Override public int lastIndexOf(Object o) { if(!(o instanceof EntityObject)) return -1; EntityObject obj = (EntityObject)o; if(obj.TD_entityGroup == null || obj.TD_entityGroup.list != this) return -1; //Same strategy as get(index), jump groups, adding their size until we reach the group containing the Object int offset = 0; int lastIndex = -1; for(EntityGroup group : localGroups) { if(obj.TD_entityGroup == group) { int index = group.entities.indexOf(obj); if(index == -1) return -1; lastIndex = offset + index; } offset += group.getEntityCount(); } return lastIndex; } @Override public Iterator<EntityObject> iterator() { if(customProfiler.reachedTile) { customProfiler.reachedTile = false; //Reset flag return new EntityIteratorTimed(this, getAge()); } return new EntityIterator(this, getAge()); } @Override public ListIterator<EntityObject> listIterator() { throw new NotImplementedException("listIterator is not implemented in TickDynamic's List implementation!"); } @Override public ListIterator<EntityObject> listIterator(int index) { throw new NotImplementedException("listIterator(index) is not implemented in TickDynamic's List implementation!"); } @Override public boolean remove(Object object) { if(!contains(object)) { if(mod.debug) System.out.println("Failed to remove: " + object + " as it does not exist in list: " + this); return false; } EntityObject entityObject = (EntityObject)object; if(entityObject.TD_entityGroup.removeEntity(entityObject)) { entityCount--; age++; return true; } if(mod.debug) System.err.println("Failed to remove: " + object + " unknown reason!"); return false; } @Override public EntityObject remove(int index) { if(mod.debug) { Thread.currentThread().dumpStack(); System.out.println("Debug Warning: Using slow remove of objects(Remove by index)!"); } EntityObject entityObject = get(index); if(remove(entityObject)) return entityObject; return null; } @Override public boolean removeAll(Collection<?> c) { //TODO: Actually make sure any was removed before returning true for(Object obj : c) remove(obj); return true; } @Override public boolean retainAll(Collection<?> c) { //TODO: Actually implement this at some time(No one uses it as far as I can see) throw new NotImplementedException("retainAll is not implemented in TickDynamic's List implementation!"); } @Override public EntityObject set(int index, EntityObject element) { //TODO: I can see absolutely no use for this in Minecraft, and any implementation would be slow(-ish) and inaccurate throw new NotImplementedException("set is not implemented in TickDynamic's List implementation!"); } @Override public int size() { return entityCount; } @Override public List<EntityObject> subList(int fromIndex, int toIndex) { throw new NotImplementedException("subList is not implemented in TickDynamic's List implementation!"); } @Override public Object[] toArray() { //Construct an array from all the groups if(mod.debug) System.out.println("SLOW toArray call on Entity/TileEntity list!"); Object[] objects = new Object[entityCount]; int offset = 0; for(EntityGroup group : localGroups) { System.arraycopy(group.entities, 0, objects, offset, group.entities.size()); offset += group.entities.size(); } return objects; } @Override public <T> T[] toArray(T[] a) { throw new NotImplementedException("toArray(T[]) is not implemented in TickDynamic's List implementation!"); } }