diff --git a/src/main/antlr/SFML.g b/src/main/antlr/SFML.g index 3e8fea57c..7f84e9076 100644 --- a/src/main/antlr/SFML.g +++ b/src/main/antlr/SFML.g @@ -64,6 +64,7 @@ boolexpr : TRUE #BooleanTrue | boolexpr AND boolexpr #BooleanConjunction | boolexpr OR boolexpr #BooleanDisjunction | setOp? labelaccess HAS itemcomparison #BooleanHas + | REDSTONE (comparisonOp number)? #BooleanRedstone ; itemcomparison : comparisonOp number item ; comparisonOp : GT diff --git a/src/main/java/ca/teamdman/sfm/common/block/ManagerBlock.java b/src/main/java/ca/teamdman/sfm/common/block/ManagerBlock.java index 3f37b400f..a6800bac6 100644 --- a/src/main/java/ca/teamdman/sfm/common/block/ManagerBlock.java +++ b/src/main/java/ca/teamdman/sfm/common/block/ManagerBlock.java @@ -15,15 +15,15 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.BaseEntityBlock; -import net.minecraft.world.level.block.EntityBlock; -import net.minecraft.world.level.block.RenderShape; -import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.*; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.BooleanProperty; import net.minecraft.world.level.material.Material; import net.minecraft.world.phys.BlockHitResult; import net.minecraftforge.network.NetworkHooks; @@ -31,18 +31,46 @@ import javax.annotation.Nullable; public class ManagerBlock extends BaseEntityBlock implements EntityBlock, ICable { + public static final BooleanProperty TRIGGERED = BlockStateProperties.TRIGGERED; + public ManagerBlock() { super(BlockBehaviour.Properties .of(Material.PISTON) .destroyTime(2) .sound(SoundType.METAL)); + registerDefaultState(getStateDefinition().any().setValue(TRIGGERED, false)); } + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + builder.add(TRIGGERED); + } public RenderShape getRenderShape(BlockState state) { return RenderShape.MODEL; } + + @Override + public void neighborChanged( + BlockState state, + Level level, + BlockPos pos, + Block block, + BlockPos neighbourPos, + boolean movedByPiston + ) { + if (!(level.getBlockEntity(pos) instanceof ManagerBlockEntity mgr)) return; + var isPowered = level.hasNeighborSignal(pos) || level.hasNeighborSignal(pos.above()); + var debounce = state.getValue(TRIGGERED); + if (isPowered && !debounce) { + mgr.trackRedstonePulseUnprocessed(); + level.setBlock(pos, state.setValue(TRIGGERED, true), 4); + } else if (!isPowered && debounce) { + level.setBlock(pos, state.setValue(TRIGGERED, false), 4); + } + } + @Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return SFMBlockEntities.MANAGER_BLOCK_ENTITY diff --git a/src/main/java/ca/teamdman/sfm/common/blockentity/ManagerBlockEntity.java b/src/main/java/ca/teamdman/sfm/common/blockentity/ManagerBlockEntity.java index 0d175e638..24ce45cb9 100644 --- a/src/main/java/ca/teamdman/sfm/common/blockentity/ManagerBlockEntity.java +++ b/src/main/java/ca/teamdman/sfm/common/blockentity/ManagerBlockEntity.java @@ -53,6 +53,19 @@ public int getCount() { return 1; } }; + private int unprocessedRedstonePulses = 0; // used by redstone trigger + + public void trackRedstonePulseUnprocessed() { + unprocessedRedstonePulses++; + } + + public void clearRedstonePulseQueue() { + unprocessedRedstonePulses = 0; + } + + public int getUnprocessedRedstonePulseCount() { + return unprocessedRedstonePulses; + } public ManagerBlockEntity(BlockPos blockPos, BlockState blockState) { super(SFMBlockEntities.MANAGER_BLOCK_ENTITY.get(), blockPos, blockState); @@ -127,6 +140,7 @@ public void syntaxError( try { programAST = new ASTBuilder().visitProgram(context); DiskItem.setProgramName(disk, programAST.name()); + programAST.addWarnings(disk); } catch (ResourceLocationException | IllegalArgumentException e) { errors.add(e.getMessage()); } catch (Throwable t) { diff --git a/src/main/java/ca/teamdman/sfm/common/program/ProgramContext.java b/src/main/java/ca/teamdman/sfm/common/program/ProgramContext.java index 28310ea5d..ceb1be213 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/ProgramContext.java +++ b/src/main/java/ca/teamdman/sfm/common/program/ProgramContext.java @@ -23,16 +23,31 @@ public class ProgramContext { private final List INPUTS = new ArrayList<>(); private final Level LEVEL; + private final int REDSTONE_PULSES; + public ProgramContext(ManagerBlockEntity manager) { - this.MANAGER = manager; - NETWORK = CableNetworkManager + this.MANAGER = manager; + NETWORK = CableNetworkManager .getOrRegisterNetwork(MANAGER) .get(); - LEVEL = MANAGER.getLevel(); + LEVEL = MANAGER.getLevel(); + REDSTONE_PULSES = MANAGER.getUnprocessedRedstonePulseCount(); + } + + public ProgramContext(ProgramContext other) { + MANAGER = other.MANAGER; + NETWORK = other.NETWORK; + LEVEL = other.LEVEL; + REDSTONE_PULSES = other.REDSTONE_PULSES; + INPUTS.addAll(other.INPUTS); + } + + public int getRedstonePulses() { + return REDSTONE_PULSES; } - public void clear() { - INPUTS.clear(); + public ProgramContext fork() { + return new ProgramContext(this); } public ManagerBlockEntity getManager() { diff --git a/src/main/java/ca/teamdman/sfm/common/program/ProgramExecutor.java b/src/main/java/ca/teamdman/sfm/common/program/ProgramExecutor.java index ed19e6ea3..a7925fae0 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/ProgramExecutor.java +++ b/src/main/java/ca/teamdman/sfm/common/program/ProgramExecutor.java @@ -19,6 +19,8 @@ public ProgramExecutor(Program program, ManagerBlockEntity manager) { public void tick() { var context = new ProgramContext(MANAGER); + + // update warnings on disk item every 20 seconds if (MANAGER .getLevel() .getGameTime() % 20 == 0) { @@ -27,6 +29,7 @@ public void tick() { .ifPresent(PROGRAM::addWarnings); } PROGRAM.tick(context); + MANAGER.clearRedstonePulseQueue(); } public Set getReferencedLabels() { diff --git a/src/main/java/ca/teamdman/sfml/ast/ASTBuilder.java b/src/main/java/ca/teamdman/sfml/ast/ASTBuilder.java index 0e3a1203c..f706d6b2d 100644 --- a/src/main/java/ca/teamdman/sfml/ast/ASTBuilder.java +++ b/src/main/java/ca/teamdman/sfml/ast/ASTBuilder.java @@ -78,6 +78,36 @@ public ASTNode visitTimerTrigger(SFMLParser.TimerTriggerContext ctx) { return new TimerTrigger(time, block); } + @Override + public ASTNode visitBooleanRedstone(SFMLParser.BooleanRedstoneContext ctx) { + ComparisonOperator comp = ComparisonOperator.GREATER_OR_EQUAL; + Quantity num = new Quantity(0); + if (ctx.comparisonOp() != null && ctx.number() != null) { + comp = visitComparisonOp(ctx.comparisonOp()); + num = visitNumber(ctx.number()); + } + + ComparisonOperator finalComp = comp; + int finalNum = num.value(); + return new BoolExpr( + programContext -> finalComp.test( + programContext + .getManager() + .getLevel() + .getBestNeighborSignal(programContext + .getManager() + .getBlockPos()), + finalNum + ) + ); + } + + @Override + public ASTNode visitPulseTrigger(SFMLParser.PulseTriggerContext ctx) { + var block = visitBlock(ctx.block()); + return new RedstoneTrigger(block); + } + @Override public Quantity visitNumber(SFMLParser.NumberContext ctx) { return new Quantity(Integer.parseInt(ctx.getText())); diff --git a/src/main/java/ca/teamdman/sfml/ast/OutputStatement.java b/src/main/java/ca/teamdman/sfml/ast/OutputStatement.java index 82b8c7821..cefdf4ede 100644 --- a/src/main/java/ca/teamdman/sfml/ast/OutputStatement.java +++ b/src/main/java/ca/teamdman/sfml/ast/OutputStatement.java @@ -7,7 +7,6 @@ import net.minecraftforge.items.IItemHandler; import java.util.List; -import java.util.stream.Collectors; import java.util.stream.Stream; public record OutputStatement( @@ -18,7 +17,7 @@ public record OutputStatement( @Override public void tick(ProgramContext context) { var inputSlots = context.getInputs().flatMap(x -> x.getSlots(context)); - var outputSlots = getSlots(context).collect(Collectors.toList()); + var outputSlots = getSlots(context).toList(); grabbing: for (var in : (Iterable) inputSlots::iterator) { for (var out : outputSlots) { diff --git a/src/main/java/ca/teamdman/sfml/ast/Program.java b/src/main/java/ca/teamdman/sfml/ast/Program.java index cd1f7e64d..7615184fc 100644 --- a/src/main/java/ca/teamdman/sfml/ast/Program.java +++ b/src/main/java/ca/teamdman/sfml/ast/Program.java @@ -33,11 +33,10 @@ public void addWarnings(ItemStack disk) { public void tick(ProgramContext context) { for (Trigger t : triggers) { if (t.shouldTick(context)) { - var start = System.nanoTime(); - t.tick(context); - context.clear(); - var end = System.nanoTime(); - var diff = end - start; +// var start = System.nanoTime(); + t.tick(context.fork()); +// var end = System.nanoTime(); +// var diff = end - start; // SFM.LOGGER.debug("Took {}ms ({}us)", diff / 1000000, diff); } } diff --git a/src/main/java/ca/teamdman/sfml/ast/RedstoneTrigger.java b/src/main/java/ca/teamdman/sfml/ast/RedstoneTrigger.java new file mode 100644 index 000000000..f0d68361b --- /dev/null +++ b/src/main/java/ca/teamdman/sfml/ast/RedstoneTrigger.java @@ -0,0 +1,20 @@ +package ca.teamdman.sfml.ast; + +import ca.teamdman.sfm.common.program.ProgramContext; + +public record RedstoneTrigger( + Block block +) implements Trigger { + + @Override + public void tick(ProgramContext context) { + for (int i = 0; i < context.getRedstonePulses(); i++) { + block.tick(context); + } + } + + @Override + public boolean shouldTick(ProgramContext manager) { + return manager.getManager().getUnprocessedRedstonePulseCount() > 0; + } +}