Skip to content

Commit

Permalink
Fixed the command executors and tile entities leaking memory. Fixed t…
Browse files Browse the repository at this point in the history
…he events firing more than they should (Mostly it was twice).

Finished basic testing the block event scripting system.
  • Loading branch information
winsock committed Feb 7, 2015
1 parent cf1fea7 commit ba1fd0f
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 79 deletions.
11 changes: 4 additions & 7 deletions src/main/java/mods/quiddity/redux/ReduxBlock.java
Expand Up @@ -59,9 +59,10 @@ public TileEntity createNewTileEntity(World worldIn, int meta) {
te = new ReduxCommandBlockTileEntity();
}

if (te != null) {
te.setupTileEntity(reduxBlock);
}
// Do initial initialization of the tile entity
if (te != null)
te.init(pack.getId(), reduxBlock);

return te;
}

Expand Down Expand Up @@ -95,8 +96,4 @@ public Class<? extends ReduxCommandBlockTileEntity> getTileEntityClass() {
protected BlockState createBlockState() {
return new BlockState(this, SUCCESS_COUNT_META);
}

public Block getReduxBlock() {
return reduxBlock;
}
}
Expand Up @@ -20,8 +20,6 @@ public ReduxCommandBlockTickableTileEntity() {}
public void update() {
if (worldObj.isRemote)
return;
if (reduxBlock == null)
setupTileEntity(((ReduxBlock) this.getWorld().getBlockState(this.pos).getBlock()).getReduxBlock());
ticks++;
if (ticks >= reduxBlock.getTickRate()) {
for (final ReduxBlockEventReceiver receiver : tickEventReceivers) {
Expand Down
156 changes: 88 additions & 68 deletions src/main/java/mods/quiddity/redux/ReduxCommandBlockTileEntity.java
@@ -1,6 +1,7 @@
package mods.quiddity.redux;

import mods.quiddity.redux.json.model.Block;
import mods.quiddity.redux.json.model.Pack;
import mods.quiddity.redux.json.model.Trigger;
import net.minecraft.block.state.IBlockState;
import net.minecraft.command.CommandResultStats;
Expand All @@ -16,20 +17,14 @@
import net.minecraft.util.IChatComponent;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.ServerChatEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.event.world.ChunkWatchEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.common.eventhandler.Event;
import net.minecraftforge.fml.common.eventhandler.EventBus;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import org.apache.logging.log4j.LogManager;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand All @@ -41,79 +36,98 @@
*/
public class ReduxCommandBlockTileEntity extends TileEntity {

private String packId = "";

protected volatile Block reduxBlock = null;
protected int lastSuccessCount = 0;
protected CommandResultStats.Type lastResultType = CommandResultStats.Type.SUCCESS_COUNT;
protected int lastResultAmount = 0;

@SuppressWarnings("all")
protected final List<ReduxBlockEventReceiver> eventReceivers = new ArrayList<ReduxBlockEventReceiver>();
protected final Set<ReduxBlockEventReceiver> tickEventReceivers = new HashSet<ReduxBlockEventReceiver>();
protected final Set<ReduxBlockEventReceiver> eventReceivers = new HashSet<ReduxBlockEventReceiver>();

public ReduxCommandBlockTileEntity() {}

public synchronized void setupTileEntity(Block reduxBlock) {
public int getLastSuccessCount() {
return lastSuccessCount;
}
public void addTickEventReceiver(ReduxBlockEventReceiver receiver) {
tickEventReceivers.add(receiver);
}

public void init(String packId, Block reduxBlock) {
this.packId = packId;
this.reduxBlock = reduxBlock;

for (Trigger trigger : reduxBlock.getScript()) {
try {
eventReceivers.add(new ReduxBlockEventReceiver(trigger));
} catch (Exception e) {
LogManager.getLogger().fatal("Error accessing FML EventBus.\nRedux will not function properly!\nDid FML Update?");
}
// We have to keep a local strong reference. Otherwise GC would remove our event receiver right away.
ReduxBlockEventReceiver receiver = new ReduxBlockEventReceiver(trigger);
eventReceivers.add(receiver);
ReduxEventDispatcher.getInstance().registerEventReceiver(receiver);
}
}

public int getLastSuccessCount() {
return lastSuccessCount;
public void setupTileEntity(String blockId) {
Pack p = Redux.instance.getReduxConfiguration().getPackFromId(packId);
if (p == null) throw new AssertionError();
for (Block b : p.getBlocks()) {
if (b.getId().equalsIgnoreCase(blockId))
reduxBlock = b;
}
if (reduxBlock == null) throw new AssertionError();

tickEventReceivers.clear();
eventReceivers.clear();

for (Trigger trigger : reduxBlock.getScript()) {
// We have to keep a local strong reference. Otherwise GC would remove our event receiver right away.
ReduxBlockEventReceiver receiver = new ReduxBlockEventReceiver(trigger);
eventReceivers.add(receiver);
ReduxEventDispatcher.getInstance().registerEventReceiver(receiver);
}
}

@Override
public void writeToNBT(NBTTagCompound compound) {
super.writeToNBT(compound);
compound.setInteger("lastSuccessCount", lastSuccessCount);
compound.setInteger(lastResultType.getTypeName(), lastResultAmount);
compound.setString("pack", packId);
compound.setString("block", reduxBlock.getId());
}

@Override
public void readFromNBT(NBTTagCompound compound) {
super.readFromNBT(compound);
this.lastSuccessCount = compound.getInteger("lastSuccessCount");

if (reduxBlock == null && this.hasWorldObj() && !this.getWorld().isRemote) {
setupTileEntity(((ReduxBlock) this.getWorld().getBlockState(this.pos).getBlock()).getReduxBlock());
}
this.packId = compound.getString("pack");
setupTileEntity(compound.getString("block"));
}

@Override
public Packet getDescriptionPacket() {
NBTTagCompound nbttagcompound = new NBTTagCompound();
this.writeToNBT(nbttagcompound);
return new S35PacketUpdateTileEntity(this.pos, 2, nbttagcompound);
}

public void addTickEventReceiver(ReduxBlockEventReceiver receiver) {
tickEventReceivers.add(receiver);
return new S35PacketUpdateTileEntity(this.pos, 0, nbttagcompound);
}

protected class ReduxBlockEventReceiver implements ICommandSender {

private final Trigger triggerScript;
private int successCount;
protected Event lastEvent = null;

public ReduxBlockEventReceiver(Trigger triggerScript) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
public ReduxBlockEventReceiver(Trigger triggerScript) {
this.triggerScript = triggerScript;

if (triggerScript.getTriggerEvent() != Event.class) {
Class<?> eventBusClass = EventBus.class;
Method privateEventRegister = eventBusClass.getDeclaredMethod("register", Class.class, Object.class, Method.class, ModContainer.class);
privateEventRegister.setAccessible(true);

Method eventReceiver = this.getClass().getDeclaredMethod("receiveEvent", Event.class);
privateEventRegister.invoke(MinecraftForge.EVENT_BUS, triggerScript.getTriggerEvent(), this, eventReceiver, FMLCommonHandler.instance().findContainerFor(Redux.instance));
} else if (ReduxCommandBlockTileEntity.this instanceof ReduxCommandBlockTickableTileEntity) {
if (ReduxCommandBlockTileEntity.this instanceof ReduxCommandBlockTickableTileEntity && triggerScript.getTriggerEvent().getForgeEventClass() == Event.class) {
ReduxCommandBlockTileEntity.this.addTickEventReceiver(this);
}
}

public Trigger getTriggerScript() {
return triggerScript;
}

public Event getLastEvent() {
return lastEvent;
}
Expand Down Expand Up @@ -198,46 +212,52 @@ private String getTriggerStringForEvent(Event event) {
return "";
}

@SubscribeEvent
public void receiveEvent(Event event) {
if (worldObj.isRemote)
return;

BlockPos blockPos = ReduxCommandBlockTileEntity.this.pos;
IBlockState defaultState = ReduxCommandBlockTileEntity.this.getWorld().getBlockState(blockPos).getBlock().getDefaultState();
// Check if the block has changed as result of an event. I.E. BlockBreak
if (ReduxCommandBlockTileEntity.this.getWorld().getBlockState(pos).getBlock().getClass() != ReduxBlock.class) {
return;
}

ICommandManager icommandmanager = FMLCommonHandler.instance().getMinecraftServerInstance().getCommandManager();
synchronized (ReduxCommandBlockTileEntity.this) {
BlockPos blockPos = ReduxCommandBlockTileEntity.this.pos;
IBlockState defaultState = ReduxCommandBlockTileEntity.this.getWorld().getBlockState(blockPos).getBlock().getDefaultState();
Stack<Integer> commandResultStack = new Stack<Integer>();

lastEvent = event;
for (String s : triggerScript.getCommands()) {
if (s.startsWith("/pop")) {
String[] split = s.split(Pattern.quote(" "));
int popCount = 1;
if (split.length == 2) {
try {
popCount = Integer.parseInt(split[1]);
} catch (NumberFormatException e) {
LogManager.getLogger().warn("Invalid /pop command issued. Command %s\nPopping once", s);
}
Stack<Integer> commandResultStack = new Stack<Integer>();

lastEvent = event;
for (String s : triggerScript.getCommands()) {
if (s.startsWith("/pop")) {
String[] split = s.split(Pattern.quote(" "));
int popCount = 1;
if (split.length == 2) {
try {
popCount = Integer.parseInt(split[1]);
} catch (NumberFormatException e) {
LogManager.getLogger().warn("Invalid /pop command issued. Command %s\nPopping once", s);
}
for (;popCount > 0; popCount--)
commandResultStack.pop();
}
for (; popCount > 0; popCount--)
commandResultStack.pop();
}

String parsedCommand = s;
while ((parsedCommand.contains("$(PEEK)") || parsedCommand.contains("$(POP)") || parsedCommand.contains("$(TRIGGER)")) && !commandResultStack.empty()) {
if (parsedCommand.contains("$(PEEK)") && (!parsedCommand.contains("$(POP)") || (parsedCommand.indexOf("$(PEEK)") < parsedCommand.indexOf("$(POP)")))) {
parsedCommand = parsedCommand.replaceFirst(Pattern.quote("$(PEEK)"), Matcher.quoteReplacement(String.valueOf(commandResultStack.peek())));
} else if (parsedCommand.contains("$(POP)")) {
parsedCommand = parsedCommand.replaceFirst(Pattern.quote("$(POP)"), Matcher.quoteReplacement(String.valueOf(commandResultStack.pop())));
} else if (parsedCommand.contains("$(TRIGGER)")) {
parsedCommand = parsedCommand.replaceFirst(Pattern.quote("$(TRIGGER)"), getTriggerStringForEvent(event));
}
String parsedCommand = s;
while ((parsedCommand.contains("$(PEEK)") || parsedCommand.contains("$(POP)") || parsedCommand.contains("$(TRIGGER)")) && !commandResultStack.empty()) {
if (parsedCommand.contains("$(PEEK)") && (!parsedCommand.contains("$(POP)") || (parsedCommand.indexOf("$(PEEK)") < parsedCommand.indexOf("$(POP)")))) {
parsedCommand = parsedCommand.replaceFirst(Pattern.quote("$(PEEK)"), Matcher.quoteReplacement(String.valueOf(commandResultStack.peek())));
} else if (parsedCommand.contains("$(POP)")) {
parsedCommand = parsedCommand.replaceFirst(Pattern.quote("$(POP)"), Matcher.quoteReplacement(String.valueOf(commandResultStack.pop())));
} else if (parsedCommand.contains("$(TRIGGER)")) {
parsedCommand = parsedCommand.replaceFirst(Pattern.quote("$(TRIGGER)"), getTriggerStringForEvent(event));
}

this.successCount = icommandmanager.executeCommand(this, parsedCommand);
commandResultStack.push(successCount);
ReduxCommandBlockTileEntity.this.getWorld().setBlockState(blockPos, defaultState.withProperty(ReduxBlock.SUCCESS_COUNT_META, successCount));
ReduxCommandBlockTileEntity.this.lastSuccessCount = this.successCount;
}

this.successCount = icommandmanager.executeCommand(this, parsedCommand);
commandResultStack.push(successCount);

ReduxCommandBlockTileEntity.this.getWorld().setBlockState(blockPos, defaultState.withProperty(ReduxBlock.SUCCESS_COUNT_META, successCount));
ReduxCommandBlockTileEntity.this.lastSuccessCount = this.successCount;
}
}
}
Expand Down
58 changes: 58 additions & 0 deletions src/main/java/mods/quiddity/redux/ReduxEventDispatcher.java
@@ -0,0 +1,58 @@
package mods.quiddity.redux;

import com.google.common.collect.ImmutableList;
import mods.quiddity.redux.json.model.Trigger;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.eventhandler.Event;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

/**
* WeakReference event dispatcher. This handles firing all of the events for Redux blocks.
* This only holds weak references to the TileEntity. This should allow it to properly unload.
*
* @author winsock on 2/7/15.
*/
public class ReduxEventDispatcher {
private static ReduxEventDispatcher ourInstance = null;

private final Map<Trigger.TriggerEvent, List<WeakReference<ReduxCommandBlockTileEntity.ReduxBlockEventReceiver>>> eventListMap
= new WeakHashMap<Trigger.TriggerEvent, List<WeakReference<ReduxCommandBlockTileEntity.ReduxBlockEventReceiver>>>();

public static ReduxEventDispatcher getInstance() {
if (ourInstance == null)
ourInstance = new ReduxEventDispatcher();
return ourInstance;
}

private ReduxEventDispatcher() {
MinecraftForge.EVENT_BUS.register(this);
}

public void registerEventReceiver(ReduxCommandBlockTileEntity.ReduxBlockEventReceiver receiver) {
if (eventListMap.get(receiver.getTriggerScript().getTriggerEvent()) == null)
eventListMap.put(receiver.getTriggerScript().getTriggerEvent(), new ArrayList<WeakReference<ReduxCommandBlockTileEntity.ReduxBlockEventReceiver>>());
eventListMap.get(receiver.getTriggerScript().getTriggerEvent()).add(new WeakReference<ReduxCommandBlockTileEntity.ReduxBlockEventReceiver>(receiver));
}

@SubscribeEvent
public void onEvent(Event event) {
if (eventListMap.get(Trigger.TriggerEvent.getTriggerEventFromForgeEvent(event.getClass())) != null) {
List<WeakReference<ReduxCommandBlockTileEntity.ReduxBlockEventReceiver>> weakReferences =
ImmutableList.copyOf(eventListMap.get(Trigger.TriggerEvent.getTriggerEventFromForgeEvent(event.getClass())));
for (WeakReference<ReduxCommandBlockTileEntity.ReduxBlockEventReceiver> eventReceiverWeakReference : weakReferences) {
ReduxCommandBlockTileEntity.ReduxBlockEventReceiver eventReceiver = eventReceiverWeakReference.get();
if (eventReceiver == null) {
eventListMap.get(Trigger.TriggerEvent.getTriggerEventFromForgeEvent(event.getClass())).remove(eventReceiverWeakReference);
continue;
}
eventReceiver.receiveEvent(event);
}
}
}
}
7 changes: 7 additions & 0 deletions src/main/java/mods/quiddity/redux/json/model/Config.java
Expand Up @@ -24,11 +24,13 @@ public class Config {

private transient List<String> humanPackNames;
private transient Map<Pack, File> packSourceFileMap;
private transient Map<String, Pack> idToPack;

public List<Pack> getPacks() {
List<Pack> parsedPacks = new ArrayList<Pack>();
humanPackNames = new ArrayList<String>();
packSourceFileMap = new HashMap<Pack, File>();
idToPack = new HashMap<String, Pack>();
for (String pack : packs) {
File file = null;
Pack p = null;
Expand Down Expand Up @@ -57,6 +59,7 @@ public List<Pack> getPacks() {
humanPackNames.add(p.getName());
// File cannot be null if p isn't null
packSourceFileMap.put(p, file);
idToPack.put(p.getId(), p);
}
}
return ImmutableList.copyOf(parsedPacks);
Expand All @@ -76,6 +79,10 @@ public File getSourceForPack(Pack p) {
return packSourceFileMap.get(p);
}

public Pack getPackFromId(String id) {
return idToPack.get(id);
}

public List<Flags<String, ?>> getFeatures() {
return ImmutableList.copyOf(features);
}
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/mods/quiddity/redux/json/model/Trigger.java
Expand Up @@ -19,8 +19,8 @@ public class Trigger {
private String trigger;
private List<String> commands;

public Class<? extends Event> getTriggerEvent() {
return TriggerEvent.valueOf(trigger).forgeEventClass;
public TriggerEvent getTriggerEvent() {
return TriggerEvent.valueOf(trigger);
}

public List<String> getCommands() {
Expand Down Expand Up @@ -48,6 +48,10 @@ private TriggerEvent(Class<? extends Event> forgeEvent) {
this.forgeEventClass = forgeEvent;
}

public Class<? extends Event> getForgeEventClass() {
return forgeEventClass;
}

public static TriggerEvent getTriggerEventFromForgeEvent(Class<? extends Event> forgeEvent) {
for (TriggerEvent e: TriggerEvent.values()) {
if (e.forgeEventClass == forgeEvent) {
Expand Down

0 comments on commit ba1fd0f

Please sign in to comment.