diff --git a/src/main/kotlin/net/shadowfacts/asmr/manager/ManagerBlockEntity.kt b/src/main/kotlin/net/shadowfacts/asmr/manager/ManagerBlockEntity.kt index f8bf87f..9dae70a 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/manager/ManagerBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/manager/ManagerBlockEntity.kt @@ -34,8 +34,8 @@ class ManagerBlockEntity: BlockEntity(ASMR.managerEntityType) { it.position = Point(240.0, 0.0) } - program.startBlock.executionFlow.link(divide) - divide.executionFlow.link(print) + program.startBlock.link(divide) + divide.link(print) } fun activate() { diff --git a/src/main/kotlin/net/shadowfacts/asmr/program/ExecutableBlock.kt b/src/main/kotlin/net/shadowfacts/asmr/program/ExecutableBlock.kt deleted file mode 100644 index c6e7df9..0000000 --- a/src/main/kotlin/net/shadowfacts/asmr/program/ExecutableBlock.kt +++ /dev/null @@ -1,14 +0,0 @@ -package net.shadowfacts.asmr.program - -import net.minecraft.util.Identifier - -/** - * @author shadowfacts - */ -abstract class ExecutableBlock(identifier: Identifier): ProgramBlock(identifier) { - - abstract val executionFlow: ExecutionFlow - - abstract fun execute() - -} \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/asmr/program/ExecutionFlow.kt b/src/main/kotlin/net/shadowfacts/asmr/program/ExecutionFlow.kt deleted file mode 100644 index f8aaee4..0000000 --- a/src/main/kotlin/net/shadowfacts/asmr/program/ExecutionFlow.kt +++ /dev/null @@ -1,22 +0,0 @@ -package net.shadowfacts.asmr.program - -/** - * @author shadowfacts - */ -interface ExecutionFlow { - val block: ExecutableBlock - - val next: ExecutableBlock? - val prev: ExecutableBlock? -} -class DirectExecutionFlow(override val block: ExecutableBlock): ExecutionFlow { - override var next: ExecutableBlock? = null - override var prev: ExecutableBlock? = null - - fun link(next: ExecutableBlock) { - this.next = next - (next.executionFlow as? DirectExecutionFlow)?.let { - it.prev = block - } - } -} diff --git a/src/main/kotlin/net/shadowfacts/asmr/program/Program.kt b/src/main/kotlin/net/shadowfacts/asmr/program/Program.kt index 88b3c0f..d74f684 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/program/Program.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/program/Program.kt @@ -1,6 +1,7 @@ package net.shadowfacts.asmr.program import net.shadowfacts.asmr.program.blocks.StartBlock +import net.shadowfacts.asmr.program.execution.ExecutableBlock import net.shadowfacts.cacao.geometry.Point /** @@ -14,11 +15,11 @@ class Program { val blocks = mutableListOf(startBlock) fun execute() { - var currentBlock: ExecutableBlock? = startBlock.executionFlow.next + var currentBlock: ExecutableBlock? = startBlock.next() while (currentBlock != null) { currentBlock.execute() - currentBlock = currentBlock.executionFlow.next + currentBlock = currentBlock.next() } } diff --git a/src/main/kotlin/net/shadowfacts/asmr/program/ProgramType.kt b/src/main/kotlin/net/shadowfacts/asmr/program/ProgramType.kt index 9dfd151..c30e2e5 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/program/ProgramType.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/program/ProgramType.kt @@ -3,7 +3,7 @@ package net.shadowfacts.asmr.program /** * @author shadowfacts */ -class ProgramType { +class ProgramType { companion object { val INT = ProgramType() val FLOAT = ProgramType() diff --git a/src/main/kotlin/net/shadowfacts/asmr/program/blocks/PrintBlock.kt b/src/main/kotlin/net/shadowfacts/asmr/program/blocks/PrintBlock.kt index a00cf3a..7cbaa81 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/program/blocks/PrintBlock.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/program/blocks/PrintBlock.kt @@ -3,14 +3,14 @@ package net.shadowfacts.asmr.program.blocks import net.minecraft.util.Identifier import net.shadowfacts.asmr.ASMR import net.shadowfacts.asmr.program.* -import net.shadowfacts.cacao.geometry.Point +import net.shadowfacts.asmr.program.execution.SimpleExecutableBlock /** * @author shadowfacts */ class PrintBlock( val type: ProgramType -): ExecutableBlock( +): SimpleExecutableBlock( Identifier(ASMR.modid, "print") ) { @@ -19,8 +19,6 @@ class PrintBlock( override val inputs: Array> = arrayOf(input) override val outputs: Array> = arrayOf() - override val executionFlow = DirectExecutionFlow(this) - override fun execute() { println(input.value) } diff --git a/src/main/kotlin/net/shadowfacts/asmr/program/blocks/StartBlock.kt b/src/main/kotlin/net/shadowfacts/asmr/program/blocks/StartBlock.kt index daecfe0..2e1eaf1 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/program/blocks/StartBlock.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/program/blocks/StartBlock.kt @@ -3,19 +3,18 @@ package net.shadowfacts.asmr.program.blocks import net.minecraft.util.Identifier import net.shadowfacts.asmr.ASMR import net.shadowfacts.asmr.program.* +import net.shadowfacts.asmr.program.execution.SimpleExecutableBlock /** * @author shadowfacts */ -class StartBlock: ExecutableBlock( +class StartBlock: SimpleExecutableBlock( Identifier(ASMR.modid, "start") ) { override val inputs: Array> = arrayOf() override val outputs: Array> = arrayOf() - override val executionFlow = DirectExecutionFlow(this) - override fun execute() { } diff --git a/src/main/kotlin/net/shadowfacts/asmr/program/blocks/math/BinaryOperatorBlock.kt b/src/main/kotlin/net/shadowfacts/asmr/program/blocks/math/BinaryOperatorBlock.kt index 42e75c6..48480d3 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/program/blocks/math/BinaryOperatorBlock.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/program/blocks/math/BinaryOperatorBlock.kt @@ -3,6 +3,7 @@ package net.shadowfacts.asmr.program.blocks.math import net.minecraft.util.Identifier import net.shadowfacts.asmr.ASMR import net.shadowfacts.asmr.program.* +import net.shadowfacts.asmr.program.execution.SimpleExecutableBlock import java.lang.RuntimeException /** @@ -11,7 +12,7 @@ import java.lang.RuntimeException class BinaryOperatorBlock( val type: ProgramType, val operation: Operation -): ExecutableBlock( +): SimpleExecutableBlock( Identifier(ASMR.modid, "binary_operator") ) { @@ -29,8 +30,6 @@ class BinaryOperatorBlock( override val inputs: Array> = arrayOf(left, right) override val outputs: Array> = arrayOf(output) - override val executionFlow = DirectExecutionFlow(this) - override fun execute() { if (type == ProgramType.INT) { val left = left.value as Int diff --git a/src/main/kotlin/net/shadowfacts/asmr/program/execution/ExecutableBlock.kt b/src/main/kotlin/net/shadowfacts/asmr/program/execution/ExecutableBlock.kt new file mode 100644 index 0000000..7877768 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/asmr/program/execution/ExecutableBlock.kt @@ -0,0 +1,20 @@ +package net.shadowfacts.asmr.program.execution + +import net.minecraft.util.Identifier +import net.shadowfacts.asmr.ASMR +import net.shadowfacts.asmr.program.ProgramBlock + +/** + * @author shadowfacts + */ +abstract class ExecutableBlock(identifier: Identifier): ProgramBlock(identifier) { + + val incoming = IncomingExecutionFlow(Identifier(ASMR.modid, "incoming"), this) + abstract val outgoing: Array + + abstract fun execute() + + abstract fun next(): ExecutableBlock? + +} + diff --git a/src/main/kotlin/net/shadowfacts/asmr/program/execution/IncomingExecutionFlow.kt b/src/main/kotlin/net/shadowfacts/asmr/program/execution/IncomingExecutionFlow.kt new file mode 100644 index 0000000..fe29500 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/asmr/program/execution/IncomingExecutionFlow.kt @@ -0,0 +1,29 @@ +package net.shadowfacts.asmr.program.execution + +import net.minecraft.util.Identifier +import net.minecraft.util.Language + +/** + * @author shadowfacts + */ +class IncomingExecutionFlow( + val identifier: Identifier, + val block: ExecutableBlock +) { + + var source: OutgoingExecutionFlow? = null + internal set + + fun link(from: OutgoingExecutionFlow) { + from.link(to = this) + } + + fun unlink() { + source?.unlink() + } + + fun translateName(): String { + return Language.getInstance().translate("programblock.execution.${identifier.namespace}.${identifier.path}") + } + +} \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/asmr/program/execution/OutgoingExecutionFlow.kt b/src/main/kotlin/net/shadowfacts/asmr/program/execution/OutgoingExecutionFlow.kt new file mode 100644 index 0000000..d112c4d --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/asmr/program/execution/OutgoingExecutionFlow.kt @@ -0,0 +1,31 @@ +package net.shadowfacts.asmr.program.execution + +import net.minecraft.util.Identifier +import net.minecraft.util.Language + +/** + * @author shadowfacts + */ +class OutgoingExecutionFlow( + val identifier: Identifier, + val block: ExecutableBlock +) { + + var destination: IncomingExecutionFlow? = null + private set + + fun link(to: IncomingExecutionFlow) { + destination = to + to.source = this + } + + fun unlink() { + destination?.source = null + destination = null + } + + fun translateName(): String { + return Language.getInstance().translate("programblock.execution.${identifier.namespace}.${identifier.path}") + } + +} \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/asmr/program/execution/SimpleExecutableBlock.kt b/src/main/kotlin/net/shadowfacts/asmr/program/execution/SimpleExecutableBlock.kt new file mode 100644 index 0000000..9ce3b85 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/asmr/program/execution/SimpleExecutableBlock.kt @@ -0,0 +1,22 @@ +package net.shadowfacts.asmr.program.execution + +import net.minecraft.util.Identifier +import net.shadowfacts.asmr.ASMR + +/** + * @author shadowfacts + */ +abstract class SimpleExecutableBlock(identifier: Identifier): ExecutableBlock(identifier) { + + val outgoingFlow = OutgoingExecutionFlow(Identifier(ASMR.modid, "outgoing"), this) + override val outgoing = arrayOf(outgoingFlow) + + override fun next(): ExecutableBlock? { + return outgoingFlow.destination?.block + } + + fun link(to: ExecutableBlock) { + outgoingFlow.link(to.incoming) + } + +} diff --git a/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramCanvasView.kt b/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramCanvasView.kt index 80f0fa2..75186c8 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramCanvasView.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramCanvasView.kt @@ -1,6 +1,7 @@ package net.shadowfacts.asmr.ui import net.shadowfacts.asmr.program.* +import net.shadowfacts.asmr.program.execution.ExecutableBlock import net.shadowfacts.asmr.ui.block.ProgramBlockParamView import net.shadowfacts.asmr.ui.block.ProgramBlockView import net.shadowfacts.cacao.geometry.Point @@ -35,13 +36,14 @@ class ProgramCanvasView(val program: Program): View() { private fun updateBlockLinks() { for ((startBlock, startView) in blocks) { - (startBlock as? ExecutableBlock)?.executionFlow?.next?.let { endBlock -> - blocks[endBlock]?.let { endView -> - val outgoing = startView.outgoingExeuction!! - val incoming = endView.incomingExecution!! - - val start = outgoing.convert(outgoing.bounds.center, to = this) - val end = incoming.convert(outgoing.bounds.center, to = this) + (startBlock as? ExecutableBlock)?.let { startBlock -> + for (outgoing in startBlock.outgoing) { + val outgoingView = startView.outgoingViews[outgoing] ?: continue + val start = outgoingView.convert(outgoingView.bounds.center, to = this) + val incoming = outgoing.destination ?: continue + val incomingBlockView = blocks[incoming.block] ?: continue + val incomingView = incomingBlockView.incomingView!! + val end = incomingView.convert(incomingView.bounds.center, to = this) executionFlowStartLinks[startBlock] = start to end } diff --git a/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockExecutionView.kt b/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockExecutionView.kt new file mode 100644 index 0000000..09135ae --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockExecutionView.kt @@ -0,0 +1,53 @@ +package net.shadowfacts.asmr.ui.block + +import net.minecraft.util.Identifier +import net.shadowfacts.asmr.ASMR +import net.shadowfacts.asmr.program.execution.IncomingExecutionFlow +import net.shadowfacts.asmr.program.execution.OutgoingExecutionFlow +import net.shadowfacts.cacao.util.texture.Texture +import net.shadowfacts.cacao.view.TextureView +import net.shadowfacts.cacao.view.View +import net.shadowfacts.kiwidsl.dsl +import java.lang.RuntimeException + +/** + * @author shadowfacts + */ +class ProgramBlockExecutionView(val incoming: IncomingExecutionFlow?, val outgoing: OutgoingExecutionFlow?): View() { + + companion object { + val emptyConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 0, v = 0) + val executionConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 7, v = 0) + } + + lateinit var textureView: TextureView + + constructor(incoming: IncomingExecutionFlow?): this(incoming, null) + constructor(outgoing: OutgoingExecutionFlow?): this(null, outgoing) + + init { + if (incoming == null && outgoing == null) { + throw RuntimeException("One of incoming or outgoing must be non-null") + } + } + + override fun wasAdded() { + super.wasAdded() + + textureView = addSubview(TextureView(emptyConnection)) + updateTexture() + + solver.dsl { + textureView.leftAnchor equalTo leftAnchor + textureView.rightAnchor equalTo rightAnchor + textureView.topAnchor equalTo topAnchor + textureView.bottomAnchor equalTo bottomAnchor + } + } + + fun updateTexture() { + val active = if (incoming != null) incoming.source != null else outgoing!!.destination != null + textureView.texture = if (active) executionConnection else emptyConnection + } + +} \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockView.kt b/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockView.kt index 082d8b8..94311a4 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockView.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockView.kt @@ -2,10 +2,11 @@ package net.shadowfacts.asmr.ui.block import net.minecraft.util.Identifier import net.shadowfacts.asmr.ASMR -import net.shadowfacts.asmr.program.ExecutableBlock +import net.shadowfacts.asmr.program.execution.ExecutableBlock import net.shadowfacts.asmr.program.ProgramBlock import net.shadowfacts.asmr.program.ProgramBlockInput import net.shadowfacts.asmr.program.ProgramBlockOutput +import net.shadowfacts.asmr.program.execution.OutgoingExecutionFlow import net.shadowfacts.cacao.geometry.Axis import net.shadowfacts.cacao.geometry.Point import net.shadowfacts.cacao.util.Color @@ -13,7 +14,6 @@ import net.shadowfacts.cacao.util.MouseButton import net.shadowfacts.cacao.util.texture.Texture import net.shadowfacts.cacao.view.Label import net.shadowfacts.cacao.view.StackView -import net.shadowfacts.cacao.view.TextureView import net.shadowfacts.cacao.view.View import net.shadowfacts.kiwidsl.dsl import no.birkett.kiwi.Constraint @@ -30,8 +30,8 @@ class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distri val parameterConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 14, v = 0) } - var incomingExecution: View? = null - var outgoingExeuction: View? = null + var incomingView: ProgramBlockExecutionView? = null + val outgoingViews = mutableMapOf() val inputViews = mutableMapOf, ProgramBlockParamView>() val outputViews = mutableMapOf, ProgramBlockParamView>() @@ -49,23 +49,33 @@ class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distri zIndex = 10.0 val titleStack = addArrangedSubview(StackView(Axis.HORIZONTAL, Distribution.CENTER)) - val title = titleStack.addArrangedSubview(Label(block.translateName(), wrappingMode = Label.WrappingMode.NO_WRAP)) if (block is ExecutableBlock) { - val incomingTexture = if (block.executionFlow.prev == null) emptyConnection else executionConnection - incomingExecution = titleStack.addArrangedSubview(TextureView(incomingTexture), index = 0) - val outgoingTexture = if (block.executionFlow.next == null) emptyConnection else executionConnection - outgoingExeuction = titleStack.addArrangedSubview(TextureView(outgoingTexture)) + incomingView = titleStack.addArrangedSubview(ProgramBlockExecutionView(block.incoming)) } + val title = titleStack.addArrangedSubview(Label(block.translateName(), wrappingMode = Label.WrappingMode.NO_WRAP)) solver.dsl { // we have to constrain the titleStack height, because it's distribution is set to CENTER, so it doesn't do it itself titleStack.heightAnchor equalTo title.heightAnchor if (block is ExecutableBlock) { - incomingExecution!!.widthAnchor equalTo incomingExecution!!.heightAnchor - incomingExecution!!.widthAnchor equalTo 7 - outgoingExeuction!!.widthAnchor equalTo outgoingExeuction!!.heightAnchor - outgoingExeuction!!.widthAnchor equalTo 7 + incomingView!!.widthAnchor equalTo incomingView!!.heightAnchor + incomingView!!.widthAnchor equalTo 7 + } + } + + if (block is ExecutableBlock) { + for (outgoing in block.outgoing) { + val hStack = addArrangedSubview(StackView(Axis.HORIZONTAL, Distribution.CENTER)) + val outgoingLabel = hStack.addArrangedSubview(Label(outgoing.translateName(), textAlignment = Label.TextAlignment.RIGHT)) + val outgoingView = hStack.addArrangedSubview(ProgramBlockExecutionView(outgoing)) + outgoingViews[outgoing] = outgoingView + solver.dsl { + hStack.heightAnchor equalTo outgoingLabel.heightAnchor + + outgoingView.widthAnchor equalTo outgoingView.heightAnchor + outgoingView.widthAnchor equalTo 7 + } } } @@ -73,8 +83,6 @@ class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distri val hStack = addArrangedSubview(StackView(Axis.HORIZONTAL, Distribution.CENTER)) block.inputs.getOrNull(i)?.let { input -> -// val inputTexture = if (input.source == null) emptyConnection else parameterConnection -// val inputView = hStack.addArrangedSubview(TextureView(inputTexture)) val inputView = hStack.addArrangedSubview(ProgramBlockParamView(input)) inputViews[input] = inputView val inputLabel = hStack.addArrangedSubview(Label(input.translateName())) @@ -93,8 +101,6 @@ class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distri block.outputs.getOrNull(i)?.let { output -> val outputLabel = hStack.addArrangedSubview(Label(output.translateName(), textAlignment = Label.TextAlignment.RIGHT)) -// val outputTexture = if (output.destinations.isEmpty()) emptyConnection else parameterConnection -// val outputView = hStack.addArrangedSubview(TextureView(outputTexture)) val outputView = hStack.addArrangedSubview(ProgramBlockParamView(output)) outputViews[output] = outputView solver.dsl { diff --git a/src/main/resources/assets/asmr/lang/en_us.json b/src/main/resources/assets/asmr/lang/en_us.json index 319f35b..4e29096 100644 --- a/src/main/resources/assets/asmr/lang/en_us.json +++ b/src/main/resources/assets/asmr/lang/en_us.json @@ -1,4 +1,6 @@ { + "programblock.execution.asmr.incoming": "Incoming", + "programblock.execution.asmr.outgoing": "Outgoing", "programblock.asmr.start": "Start", "programblock.asmr.constant": "Constant", "programblock.param.asmr.constant.output": "Output", diff --git a/src/test/kotlin/net/shadowfacts/asmr/program/ProgramTests.kt b/src/test/kotlin/net/shadowfacts/asmr/program/ProgramTests.kt index 8c52c91..190ae83 100644 --- a/src/test/kotlin/net/shadowfacts/asmr/program/ProgramTests.kt +++ b/src/test/kotlin/net/shadowfacts/asmr/program/ProgramTests.kt @@ -20,7 +20,7 @@ class ProgramTests { it.right.link(from = two.output) } - program.startBlock.executionFlow.link(multiply) + program.startBlock.link(multiply) program.execute()