This commit is contained in:
2026-04-26 23:32:04 +02:00
25 changed files with 405 additions and 341 deletions

40
TODO.md Normal file
View File

@@ -0,0 +1,40 @@
# Networking
> Pretty important here
## Auto-connect
Rework the auto-connect system of device blocks to be more stable.
Prob re-scan the network continuously
## Synchronization
Move the state logic into the node, as it was meant to be.
Note, addresses on the client are allowed to be complete bullshit at least for a bit of time.
`ComponentItem`s should no longer allow a node on the client (at least for now), it serves no
purpose currently, and also is not designed well.
## Get rid of NodeBlockEntity
It is basically throw-away stitched together garbage.
Replace it with a new abstract class, which allows different nodes on different sides,
handles synchronizing those nodes according to their state changes.
Also implement an equivalent for LivingEntities, like drones or other addon stuff.
## Optimizations
Optimize the networking, both synchronization using smaller encodings,
and emitting messages or adding/removing nodes. The goal is to have as little of
the CPU time on the server thread taken by NC as we can.
# Computation
> Pretty important for a computer mod
## JNI
We need the JNI system so we can salvage our hard labor thrown into NeoNucleus.
Also because it is a capable engine and has a good API for architectures, and NCL is very capable.
## Worker threads
Computers need worker threads for running non-synchronized code, because otherwise we're cooked

View File

@@ -4,38 +4,35 @@ import net.minecraft.client.player.LocalPlayer
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.ChatType
import net.minecraft.network.chat.OutgoingChatMessage
import net.minecraft.network.chat.PlayerChatMessage
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.BlockHitResult
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.PowerRole
import kotlin.math.min
open class CapacitorEntity(val capacity: Long, type: BlockEntityType<*>, pos: BlockPos, state: BlockState) : NodeBlockEntity(type, pos, state) {
override val node = object : Networking.Node() {
override val deviceNode = object : DeviceNode() {
override var powerRole = PowerRole.STORAGE
override var energyCapacity: Long = capacity
}
override fun loadAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.loadAdditional(compoundTag, provider)
node.energy = min(compoundTag.getLong("energy"), node.energyCapacity)
deviceNode.energy = min(compoundTag.getLong("energy"), deviceNode.energyCapacity)
}
override fun saveAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.saveAdditional(compoundTag, provider)
compoundTag.putLong("energy", node.energy)
compoundTag.putLong("energy", deviceNode.energy)
}
}
@@ -65,8 +62,8 @@ class CapacitorBlock(val tier: Int) : NodeBlock() {
val p = player as LocalPlayer
val ent = level.getBlockEntity(blockPos)
if(ent is CapacitorEntity) {
if(p.isCrouching) ent.node.giveEnergy(1)
val msg = PlayerChatMessage.system("energy: ${ent.node.energy} / ${ent.capacity} (${ent.computeEdges().size} edges, ${ent.node.getReachable().size} connected)")
if(p.isCrouching) ent.deviceNode.giveEnergy(1)
val msg = PlayerChatMessage.system("energy: ${ent.deviceNode.energy} / ${ent.capacity} (${ent.computeEdges().size} edges, ${ent.deviceNode.getReachable().size} connected)")
p.sendSystemMessage(OutgoingChatMessage.create(msg).content())
}
}

View File

@@ -21,6 +21,7 @@ import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.DeviceNode
import java.time.Duration
object NodeSynchronizer {
@@ -148,30 +149,30 @@ object NodeSynchronizer {
}
abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState) : BlockEntity(blockEntityType, blockPos, blockState) {
abstract val node: Networking.Node
abstract val deviceNode: DeviceNode
fun initNetworking(): NodeBlockEntity {
Networking.addNode(node)
Networking.addNode(deviceNode)
invalidateNodeState()
return this
}
// runs on the server, meant to encode state to send to all players
open fun encodeDownstreamData(packet: FriendlyByteBuf) {
packet.writeUUID(node.address)
packet.writeLong(node.energy)
packet.writeLong(node.energyCapacity)
packet.writeEnum(node.reachability)
packet.writeEnum(node.powerRole)
packet.writeUUID(deviceNode.address)
packet.writeLong(deviceNode.energy)
packet.writeLong(deviceNode.energyCapacity)
packet.writeEnum(deviceNode.reachability)
packet.writeEnum(deviceNode.powerRole)
}
// runs on the client, meant to decode server state packets to synchronize client state
open fun syncWithUpstream(packet: FriendlyByteBuf) {
Networking.changeNodeAddress(node, packet.readUUID())
node.energy = packet.readLong()
node.energyCapacity = packet.readLong()
node.reachability = packet.readEnum(node.reachability.javaClass)
node.powerRole = packet.readEnum(node.powerRole.javaClass)
Networking.changeNodeAddress(deviceNode, packet.readUUID())
deviceNode.energy = packet.readLong()
deviceNode.energyCapacity = packet.readLong()
deviceNode.reachability = packet.readEnum(deviceNode.reachability.javaClass)
deviceNode.powerRole = packet.readEnum(deviceNode.powerRole.javaClass)
}
// Encodes data meant for the associated screen of a player
@@ -223,7 +224,7 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl
if(!stateIsDirty) return
stateIsDirty = false
computeEdges().forEach {
node.connectTo(it.node)
deviceNode.connectTo(it.deviceNode)
}
}
@@ -235,7 +236,7 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl
override fun setRemoved() {
super.setRemoved()
Networking.removeNode(node)
Networking.removeNode(deviceNode)
}
override fun clearRemoved() {
@@ -259,7 +260,7 @@ abstract class NodeBlock(properties: Properties = Properties.of()): BaseBlock(pr
return object : BlockEntityTicker<T> {
override fun tick(level: Level, blockPos: BlockPos, blockState: BlockState, blockEntity: T) {
if(blockEntity !is NodeBlockEntity) return
if(Networking.getNode(blockEntity.node.address) == null) blockEntity.initNetworking()
if(Networking.getNode(blockEntity.deviceNode.address) == null) blockEntity.initNetworking()
blockEntity.tickNode(level)
}
}

View File

@@ -7,14 +7,13 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.RedStoneWireBlock
import net.minecraft.world.level.block.RedstoneTorchBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.BlockHitResult
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.DeviceNode
fun dirToIdx(direction: Direction) = Direction.entries.indexOf(direction)
@@ -23,7 +22,7 @@ class RedstoneIOEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnt
val redstoneOut = Array<Int>(Direction.entries.size) {0}
// TODO: have redstone I/O node for component and shi
override val node = object : Networking.Node() {
override val deviceNode = object : DeviceNode() {
}
@@ -42,7 +41,7 @@ class RedstoneIOEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnt
}
fun onRedstoneSignalChanged(dir: Direction, oldValue: Int, newValue: Int) {
Networking.emitMessage(node, Networking.ComputerUncheckedSignal(node, "redstone_changed", arrayOf(node.address.toString(), dirToIdx(dir), oldValue, newValue)))
Networking.emitMessage(deviceNode, Networking.ComputerUncheckedSignal(deviceNode, "redstone_changed", arrayOf(deviceNode.address.toString(), dirToIdx(dir), oldValue, newValue)))
NeoComputers.LOGGER.info("redstone in direction ${dir.name} changed from $oldValue to $newValue")
}
}

View File

@@ -7,32 +7,23 @@ import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.MenuProvider
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.block.state.properties.EnumProperty
import net.minecraft.world.level.block.state.properties.EnumProperty.*
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.phys.BlockHitResult
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.entity.ScreenEntity
import org.neoflock.neocomputers.gui.menu.Menus
import org.neoflock.neocomputers.gui.menu.ScreenMenu
import org.neoflock.neocomputers.network.Networking
import kotlin.math.abs
import kotlin.math.max
@@ -62,7 +53,7 @@ class ScreenBlock() : NodeBlock() {
): InteractionResult {
if(!level.isClientSide) {
val screenState = level.getBlockEntity(blockPos, BlockEntities.SCREEN_ENTITY.get()).get()
if(!screenState.node.consumeEnergy(ENERGY)) {
if(!screenState.deviceNode.consumeEnergy(ENERGY)) {
player.sendSystemMessage(Component.literal("Not enough power."))
return InteractionResult.SUCCESS
};

View File

@@ -25,6 +25,7 @@ import org.neoflock.neocomputers.block.NodeSynchronizer
import org.neoflock.neocomputers.block.dirToIdx
import org.neoflock.neocomputers.gui.menu.CaseMenu
import org.neoflock.neocomputers.item.ComponentItem
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.PowerRole
import org.neoflock.neocomputers.sounds.ComputerRunningSoundInstance
@@ -44,7 +45,7 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
var arch = "Lua 5.3"
var soundInstance: SoundInstance? = null
override val node = object : Networking.Node() {
override val deviceNode = object : DeviceNode() {
override var powerRole = PowerRole.CONSUMER
override var energyCapacity: Long = 500
}
@@ -79,8 +80,8 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
super.encodeScreenData(player, packet)
packet.writeBoolean(isOn)
packet.writeByteArray((err ?: "").encodeToByteArray())
packet.writeLong(node.energy)
packet.writeLong(node.energyCapacity)
packet.writeLong(deviceNode.energy)
packet.writeLong(deviceNode.energyCapacity)
packet.writeLong(getMachineMemoryUsed())
packet.writeLong(getMachineMemoryTotal())
packet.writeLong(getMachineComponentsUsed())
@@ -112,12 +113,12 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
item.onMachineEvent(it, this, event)
}
}
Networking.emitMessage(node, Networking.ComputerEvent(node, event))
Networking.emitMessage(deviceNode, Networking.ComputerEvent(deviceNode, event))
}
fun onRedstoneSignalChanged(dir: Direction, oldValue: Int, newValue: Int) {
sendMachineEvent(MachineRedstoneEvent(this, dir, oldValue, newValue))
Networking.emitMessage(node, Networking.ComputerUncheckedSignal(node, "redstone_changed", arrayOf(node.address.toString(), dirToIdx(dir), oldValue, newValue)))
Networking.emitMessage(deviceNode, Networking.ComputerUncheckedSignal(deviceNode, "redstone_changed", arrayOf(deviceNode.address.toString(), dirToIdx(dir), oldValue, newValue)))
NeoComputers.LOGGER.info("redstone in direction ${dir.name} changed from $oldValue to $newValue")
if(oldValue == 0) {
// Rising edge
@@ -133,7 +134,7 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
fun setRunning(value: Boolean) {
if(isOn == value) return
NeoComputers.LOGGER.info("[${node.address}] Going from $isOn to $value")
NeoComputers.LOGGER.info("[${deviceNode.address}] Going from $isOn to $value")
isOn = value
val world = level ?: return
blockState?.setValue(CaseBlock.COMPUTER_RUNNING, isOn)
@@ -167,10 +168,10 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
return false
}
// less than 20% energy is bad
if(node.energy < node.energyCapacity/5) {
if(deviceNode.energy < deviceNode.energyCapacity/5) {
crash("@neocomputers.errors.ENOENJ")
// we add a beep for the special case where we do have a little bit of energy :P
if(node.energy > 0) beepAsync("..")
if(deviceNode.energy > 0) beepAsync("..")
return false
}
if(getMachineMemoryTotal() == 0L) {
@@ -205,7 +206,7 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
override fun getLastError(): String? = err
override fun getMachineNode(): Networking.Node = node
override fun getMachineNode() = deviceNode
override fun getRedstoneInput(direction: Direction): Int = redstoneIn[dirToIdx(direction)]
@@ -233,7 +234,7 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
override fun getMachineMemoryTotal(): Long = stacks.mapNotNull { (it.item as? ComponentItem)?.getMemoryCapacity(it) }.sum().toLong()
override fun getMachineMemoryUsed(): Long = 0
override fun getMachineComponentsUsed(): Long = node.getReachable().size.toLong()
override fun getMachineComponentsUsed(): Long = deviceNode.getReachable().size.toLong()
override fun getMachineComponentsTotal(): Long = stacks.mapNotNull { (it.item as? ComponentItem)?.getComponentCapacity(it) }.sum().toLong()
override fun getMachineArchitecture() = arch
override fun getMachineArchitectures() = stacks.mapNotNull { (it.item as? ComponentItem)?.getArchitecturesProvided(it) }.flatten().toSet()
@@ -252,14 +253,14 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
override fun loadAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.loadAdditional(compoundTag, provider)
node.energy = min(node.energyCapacity, compoundTag.getLong("energy"))
deviceNode.energy = min(deviceNode.energyCapacity, compoundTag.getLong("energy"))
//isOn = compoundTag.getBoolean("powerOn")
ContainerHelper.loadAllItems(compoundTag, getItems(), provider)
}
override fun saveAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.saveAdditional(compoundTag, provider)
compoundTag.putLong("energy", node.energy)
compoundTag.putLong("energy", deviceNode.energy)
//compoundTag.putBoolean("powerOn", isOn)
ContainerHelper.saveAllItems(compoundTag, getItems(), provider)
}
@@ -278,11 +279,12 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
if (isRunning()) {
if(diskActivityTime > 0) diskActivityTime--
if(networkActivityTime > 0) networkActivityTime--
if(getMachineComponentsUsed() > getMachineComponentsTotal()) {
crash("too many components")
}
if (!node.consumeEnergy(1)) {
crash("out of energy")
if(getMachineArchitectures().isEmpty()) {
crash("@neocomputers.errors.ENOCPU")
} else if(getMachineComponentsUsed() > getMachineComponentsTotal()) {
crash("@neocomputers.errors.E2BIG")
} else if (!deviceNode.consumeEnergy(1)) {
crash("@neocomputers.errors.ENOENJ")
}
}
}

View File

@@ -17,6 +17,7 @@ import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.block.CombustionGeneratorBlock
import org.neoflock.neocomputers.block.NodeBlockEntity
import org.neoflock.neocomputers.gui.menu.CombustionGeneratorMenu
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.PowerRole
import org.neoflock.neocomputers.utils.GenericContainer
@@ -28,7 +29,7 @@ class CombustionGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
var burningTimeRemaining: Int = 0
override val node = object : Networking.Node() {
override val deviceNode = object : DeviceNode() {
override var powerRole = PowerRole.GENERATOR
override var energyCapacity: Long = 100000
}
@@ -52,13 +53,13 @@ class CombustionGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
// keep combusting and shi
if(burningTimeRemaining > 0) {
burningTimeRemaining--
node.giveEnergy(energyPerTick)
deviceNode.giveEnergy(energyPerTick)
setChanged()
return
}
// no point
if(node.energy >= node.energyCapacity) return;
if(deviceNode.energy >= deviceNode.energyCapacity) return;
// :fire:
val fuel = stacks[0]
@@ -79,20 +80,20 @@ class CombustionGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
}
override fun encodeScreenData(player: ServerPlayer, packet: FriendlyByteBuf) {
packet.writeLong(node.energy)
packet.writeLong(node.energyCapacity)
packet.writeLong(deviceNode.energy)
packet.writeLong(deviceNode.energyCapacity)
}
override fun loadAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.loadAdditional(compoundTag, provider)
node.energy = min(node.energyCapacity, compoundTag.getLong("energy"))
deviceNode.energy = min(deviceNode.energyCapacity, compoundTag.getLong("energy"))
burningTimeRemaining = compoundTag.getInt("burningTimeRemaining")
ContainerHelper.loadAllItems(compoundTag, getItems(), provider)
}
override fun saveAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.saveAdditional(compoundTag, provider)
compoundTag.putLong("energy", node.energy)
compoundTag.putLong("energy", deviceNode.energy)
compoundTag.putInt("burningTimeRemaining", burningTimeRemaining)
ContainerHelper.saveAllItems(compoundTag, getItems(), provider)
}

View File

@@ -5,6 +5,7 @@ import net.minecraft.core.Direction
import net.minecraft.world.level.Level
import net.minecraft.world.phys.Vec3
import org.neoflock.neocomputers.item.ComponentItem
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.Networking
import java.time.Duration
@@ -39,7 +40,7 @@ interface MachineEntity {
fun crash(error: String): Boolean
fun getLastError(): String?
fun getMachineNode(): Networking.Node
fun getMachineNode(): DeviceNode
// Some metadata
fun getMachineMemoryTotal(): Long

View File

@@ -1,8 +1,6 @@
package org.neoflock.neocomputers.entity;
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
@@ -11,15 +9,15 @@ import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.NodeBlockEntity
import org.neoflock.neocomputers.gui.buffer.BufferRenderer
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.PowerRole
import org.neoflock.neocomputers.utils.GPUChar
import org.neoflock.neocomputers.utils.TextBuffer
class ScreenEntity(blockPos: BlockPos, blockState: BlockState) :
NodeBlockEntity(BlockEntities.SCREEN_ENTITY.get(), blockPos, blockState) {
override val node = object : Networking.Node() {
override val deviceNode = object : DeviceNode() {
override fun received(message: Networking.Message) {
super.received(message)
if(message is Networking.ComputerEvent) {
@@ -71,8 +69,7 @@ class ScreenEntity(blockPos: BlockPos, blockState: BlockState) :
}
private fun createscreenstuffs() {
bound = "screen/"+node.address.toString().replace("-", "_")
// NeoComputers.LOGGER.info(bound)
bound = "screen/"+deviceNode.address.toString().replace("-", "_")
if (level!!.isClientSide) {
var renderer = BufferRenderer(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, bound), textBuf)
renderer.drawBuffer()

View File

@@ -6,13 +6,14 @@ import net.minecraft.nbt.CompoundTag
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.block.NodeBlockEntity
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.PowerRole
class SolarGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : NodeBlockEntity(BlockEntities.SOLARGEN_ENTITY.get(), blockPos, blockState) {
val energyPerTick: Long = 50
override val node = object : Networking.Node() {
override val deviceNode = object : DeviceNode() {
override var powerRole: PowerRole = PowerRole.GENERATOR
override var energyCapacity: Long = 50000
}
@@ -21,17 +22,17 @@ class SolarGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : No
super.tickNode(level)
val l = level
if(l.isDay) {
node.giveEnergy(energyPerTick)
deviceNode.giveEnergy(energyPerTick)
}
}
override fun loadAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.loadAdditional(compoundTag, provider)
node.energy = compoundTag.getLong("energy")
deviceNode.energy = compoundTag.getLong("energy")
}
override fun saveAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.saveAdditional(compoundTag, provider)
compoundTag.putLong("energy", node.energy)
compoundTag.putLong("energy", deviceNode.energy)
}
}

View File

@@ -7,13 +7,13 @@ import org.neoflock.neocomputers.gui.widget.ComponentRoles
import org.neoflock.neocomputers.network.Networking
open class CBUSItem(val tier: Int, val maxComponents: Int): Item(Item.Properties()), ComponentItem {
override fun getComponentRoles(itemStack: ItemStack): Set<String> = setOf(ComponentRoles.BUS)
override fun getComponentRoles(itemStack: ItemStack) = setOf(ComponentRoles.BUS)
override fun getComponentTier(itemStack: ItemStack): Int = tier
override fun getComponentTier(itemStack: ItemStack) = tier
override fun getComponentCapacity(itemStack: ItemStack): Int = maxComponents
override fun getComponentCapacity(itemStack: ItemStack) = maxComponents
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?): Networking.Node? = null
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?) = null
}
class CBUS0: CBUSItem(1, 8)
class CBUS1: CBUSItem(2, 12)

View File

@@ -15,7 +15,7 @@ open class CPUItem(val tier: Int, val maxComponents: Int): Item(Item.Properties(
override fun getArchitecturesProvided(itemStack: ItemStack): Set<String> = setOf("Lua 5.3")
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?): Networking.Node? = null
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?) = null
}
class CPU0: CPUItem(1, 8)

View File

@@ -3,6 +3,7 @@ package org.neoflock.neocomputers.item
import net.minecraft.world.item.ItemStack
import org.neoflock.neocomputers.entity.MachineEntity
import org.neoflock.neocomputers.entity.MachineEvent
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.Networking
import java.util.UUID
@@ -33,10 +34,10 @@ interface ComponentItem {
}
// To node, if applicable. Meant to create the node, but not add it, as it will use the itemStack's address to find it again
fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?): Networking.Node?
fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?): DeviceNode?
// Gets the node associated to an item, if it exists
fun getComponentNode(itemStack: ItemStack): Networking.Node? {
fun getComponentNode(itemStack: ItemStack): DeviceNode? {
val address = itemStack.get(DataComponents.ADDRESS) ?: return null
val uuid = UUID.fromString(address) ?: return null
return Networking.getNode(uuid)

View File

@@ -22,7 +22,7 @@ open class DataCard(val tier: Int, val limit: Long): Item(Properties()), Compone
}
// TODO: Modem Component
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?): Networking.Node? = null
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?) = null
override fun appendHoverText(
itemStack: ItemStack,

View File

@@ -32,7 +32,7 @@ open class EEPROMItem(val tier: Int, val codeCapacity: Int, val dataCapacity: In
super.whenComponentPlaced(itemStack, machine, newRole)
}
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?): Networking.Node? = null
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?) = null
override fun appendHoverText(
itemStack: ItemStack,

View File

@@ -21,7 +21,7 @@ open class GPUCard(val tier: Int, val vram: Long): Item(Properties()), Component
}
// TODO: GPU Component
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?): Networking.Node? = null
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?) = null
override fun appendHoverText(
itemStack: ItemStack,

View File

@@ -28,7 +28,7 @@ open class HardDiskItem(val tier: Int, val capacity: Long): Item(getDiskProperti
super.whenComponentPlaced(itemStack, machine, newRole)
}
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?): Networking.Node? = null
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?) = null
override fun appendHoverText(
itemStack: ItemStack,

View File

@@ -20,7 +20,7 @@ class InternetCard: Item(Item.Properties()), ComponentItem {
}
// TODO: Internet Component
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?): Networking.Node? = null
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?) = null
override fun appendHoverText(
itemStack: ItemStack,

View File

@@ -6,6 +6,7 @@ import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import org.neoflock.neocomputers.entity.MachineEntity
import org.neoflock.neocomputers.gui.widget.ComponentRoles
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.utils.Formatting
@@ -19,7 +20,7 @@ open class MemoryItem(val tier: Int, val capacity: Int): Item(Item.Properties().
override fun getComponentCapacity(itemStack: ItemStack): Int = 0
// no node for memory
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?): Networking.Node? = null
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?) = null
override fun appendHoverText(
itemStack: ItemStack,

View File

@@ -19,7 +19,7 @@ open class NetworkCard(val tier: Int, val maxRange: Int, val isWired: Boolean):
}
// TODO: Modem Component
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?): Networking.Node? = null
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?) = null
override fun appendHoverText(
itemStack: ItemStack,

View File

@@ -21,7 +21,7 @@ open class RedstoneCard(val tier: Int): Item(Properties()), ComponentItem {
}
// TODO: Redstone Component
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?): Networking.Node? = null
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?) = null
override fun appendHoverText(
itemStack: ItemStack,

View File

@@ -20,7 +20,7 @@ class TunnelCard: Item(Properties().component(DataComponents.TUNNEL_CHANNEL, "cr
}
// TODO: Tunnel Component
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?): Networking.Node? = null
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity?) = null
override fun appendHoverText(
itemStack: ItemStack,

View File

@@ -0,0 +1,235 @@
package org.neoflock.neocomputers.network
import net.minecraft.core.BlockPos
import net.minecraft.network.FriendlyByteBuf
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.network.Networking.Message
import org.neoflock.neocomputers.network.Networking.Visibility
import org.neoflock.neocomputers.network.Networking.maxHopCount
import java.util.UUID
import kotlin.math.min
open class DeviceNode(_address: UUID? = null) {
val connections = HashSet<DeviceNode>()
private var reachableCache: Set<DeviceNode>? = null
var address = _address ?: UUID.randomUUID()
open var reachability = Visibility.NETWORK
open var powerRole = PowerRole.CONSUMER
open var energy: Long = 0
open var energyCapacity: Long = 0
// give energy, returns how much was actually given
// cannot exceed amount specified
open fun giveEnergy(amount: Long): Long {
val maximum = min(amount, energyCapacity - energy)
energy += maximum
markChanged()
return maximum
}
// take energy out, returns how much was actually taken
// cannot exceed amount specified
open fun withdrawEnergy(amount: Long): Long {
val maximum = min(amount, energy)
energy -= maximum
markChanged()
return maximum
}
fun getChargerNodes(): Set<DeviceNode> = getReachable().filter { it.powerRole != PowerRole.CONSUMER }.toSet()
fun totalEnergyInConnections(): Long = getChargerNodes().fold(0) { acc, node -> acc + node.energy }
fun maxEnergyInConnections(): Long = getChargerNodes().fold(0) { acc, node -> acc + node.energyCapacity }
// attempts to consume
fun consumeEnergy(energy: Long): Boolean {
// consumes energy, returns false if not enough
val total = totalEnergyInConnections() + this.energy
if(energy > total) return false
var remaining = energy
remaining -= withdrawEnergy(remaining)
if(remaining <= 0) return true
for (charger in getChargerNodes()) {
if(remaining <= 0) break
remaining -= charger.withdrawEnergy(remaining)
}
return true
}
// PLEASE only call if consumer, in the name of all that is holy
fun tryToChargeFully() {
var remaining = energyCapacity - energy
if(remaining <= 0) return
for (charger in getChargerNodes()) {
if(remaining <= 0) break
val amount = charger.withdrawEnergy(remaining)
val given = giveEnergy(amount)
remaining -= given
if(given < amount) {
val delta = amount - given // amount lost while given back
if(charger.giveEnergy(delta) < delta) {
NeoComputers.LOGGER.warn("LOSING ENERGY! Tried giving $delta back to $charger and we're losing our marbles!")
}
}
}
}
// only call if storage
fun balanceStorage() {
for(battery in getReachable()) {
if(battery.powerRole != PowerRole.STORAGE) continue
// its so if for example we have a battery with 2x the capacity
// we don't try to even the energy between them since that's just bad
// and might pointless delete energy over time
val capacityRatio = energyCapacity.toDouble() / battery.energyCapacity
val meaningfulSurplus = (battery.energy * capacityRatio - energy).toLong()
if(meaningfulSurplus <= 0) {
// WE'RE greedy (or negligible surplus)? Do nothing
continue
}
// steal from this greedy mf
val toSteal = meaningfulSurplus / 2
if(toSteal == 0L) continue // broke storahh
val stolen = battery.withdrawEnergy(toSteal)
if(giveEnergy(stolen) < stolen) {
NeoComputers.LOGGER.warn("LOSING ENERGY IN NODE $address!!!! THIS IS REALLY BAD!!!")
}
}
}
// rob the generators
fun stealGeneratorPower() {
var remaining = energyCapacity - energy
for(generator in getReachable()) {
if(generator.powerRole != PowerRole.GENERATOR) continue
// rob this mf
val robbed = generator.withdrawEnergy(remaining)
val taken = giveEnergy(robbed)
if(taken < robbed) {
NeoComputers.LOGGER.warn("energy caught being DELETED in the big 26")
}
remaining -= taken
}
}
open fun tick() {
if(powerRole == PowerRole.CONSUMER) tryToChargeFully()
if(powerRole == PowerRole.STORAGE) {
stealGeneratorPower()
balanceStorage()
}
}
// processes a received message
open fun received(message: Message) {}
// called when a new direct connection is made
open fun onConnect(deviceNode: DeviceNode) {}
// called when a direct connection is lost
open fun onDisconnect(deviceNode: DeviceNode) {}
// called when a new node is added globally
open fun onNodeAdded(deviceNode: DeviceNode) {
invalidateReachableCache()
}
// called when a node is removed globally
open fun onNodeRemoved(deviceNode: DeviceNode) {
invalidateReachableCache()
}
fun getReachable(): Set<DeviceNode> {
if(reachableCache == null) {
reachableCache = computeReachable();
}
return reachableCache!!;
}
open fun invalidateReachableCache() {
reachableCache = null
}
fun computeReachable(): Set<DeviceNode> {
if(reachability == Visibility.NONE) {
return setOf();
}
if(reachability == Visibility.DIRECT) {
return connections.minus(this);
}
if(reachability == Visibility.NETWORK) {
// absolute cinema
val working = HashSet<DeviceNode>();
val pending = mutableListOf(this);
var iterCount = 0;
while(iterCount < maxHopCount && pending.isNotEmpty()) {
iterCount++;
val subnode = pending.removeFirst()
if(subnode in working) continue
working.add(subnode)
if(subnode.reachability == Visibility.NETWORK) {
pending.addAll(subnode.connections)
} else if(subnode.reachability == Visibility.DIRECT) {
working.addAll(subnode.connections)
}
}
// cannot send to itself!
working.remove(this);
return working;
}
throw NotImplementedError("visibility not implemented");
}
fun connectTo(other: DeviceNode) {
this.directConnectTo(other);
other.directConnectTo(this);
}
fun disconnectFrom(other: DeviceNode) {
this.directDisconnectFrom(other);
other.directDisconnectFrom(this);
}
fun directConnectTo(other: DeviceNode) {
if(other == this) return;
if(other in connections) return;
connections.add(other);
this.onConnect(other);
}
fun directDisconnectFrom(other: DeviceNode) {
if(other !in connections) return;
connections.remove(other);
this.onDisconnect(other);
}
// Network synchronization with the NodeSynchronizer
// TODO: process shi
var outOfSync = true
fun markChanged() {
outOfSync = true
}
open fun encodeScreenData(buf: FriendlyByteBuf) {}
// Meant to write the entire state as a single commit
// for clients which say they have no fucking idea what the server is storing
open fun writeFullStateCommit(buf: FriendlyByteBuf) {}
// client-side, meant to bring state forward
open fun processCommit(buf: FriendlyByteBuf) {}
}
abstract class WirelessEndpoint(address: UUID?) : DeviceNode(address) {
abstract fun getRange(): Double
abstract fun getDimension(): Int
abstract fun getPosition(): BlockPos
// separate from process for simplicity
abstract fun receiveWireless(message: Message, emitter: WirelessEndpoint)
}

View File

@@ -1,7 +1,6 @@
package org.neoflock.neocomputers.network
import net.minecraft.core.BlockPos
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.entity.MachineEvent
import java.util.UUID
import kotlin.math.min
@@ -35,226 +34,24 @@ object Networking {
}
abstract class Message(val sender: Node)
abstract class Message(val sender: DeviceNode)
class ClassicPacket(sender: Node, val src: String, val dst: String, val port: Int, val data: List<Any>, val hopCount: Int) : Message(sender) {
class ClassicPacket(sender: DeviceNode, val src: String, val dst: String, val port: Int, val data: List<Any>, val hopCount: Int) : Message(sender) {
fun hop() = ClassicPacket(sender, src, dst, port, data, hopCount + 1);
}
// for plugins and shi
class ComputerCheckedSignal(sender: Node, val player: String?, val name: String, val data: Array<Any>): Message(sender)
class ComputerUncheckedSignal(sender: Node, val name: String, val data: Array<Any>): Message(sender)
class ComputerEvent(sender: Node, val machineEvent: MachineEvent): Message(sender)
open class Node(_address: UUID? = null) {
val connections = mutableSetOf<Node>()
private var reachableCache: Set<Node>? = null
var address = _address ?: UUID.randomUUID()
open var reachability = Visibility.NETWORK
open var powerRole = PowerRole.CONSUMER
open var energy: Long = 0
open var energyCapacity: Long = 0
// give energy, returns how much was actually given
// cannot exceed amount specified
open fun giveEnergy(amount: Long): Long {
val maximum = min(amount, energyCapacity - energy)
energy += maximum
return maximum
}
// take energy out, returns how much was actually taken
// cannot exceed amount specified
open fun withdrawEnergy(amount: Long): Long {
val maximum = min(amount, energy)
energy -= maximum
return maximum
}
fun getChargerNodes(): Set<Node> = getReachable().filter { it.powerRole != PowerRole.CONSUMER }.toSet()
fun totalEnergyInConnections(): Long = getChargerNodes().fold(0) { acc, node -> acc + node.energy }
fun maxEnergyInConnections(): Long = getChargerNodes().fold(0) { acc, node -> acc + node.energyCapacity }
// attempts to consume
fun consumeEnergy(energy: Long): Boolean {
// consumes energy, returns false if not enough
val total = totalEnergyInConnections() + this.energy
if(energy > total) return false
var remaining = energy
remaining -= withdrawEnergy(remaining)
if(remaining <= 0) return true
for (charger in getChargerNodes()) {
if(remaining <= 0) break
remaining -= charger.withdrawEnergy(remaining)
}
return true
}
// PLEASE only call if consumer, in the name of all that is holy
fun tryToChargeFully() {
var remaining = energyCapacity - energy
if(remaining <= 0) return
for (charger in getChargerNodes()) {
if(remaining <= 0) break
val amount = charger.withdrawEnergy(remaining)
val given = giveEnergy(amount)
remaining -= given
if(given < amount) {
val delta = amount - given // amount lost while given back
if(charger.giveEnergy(delta) < delta) {
NeoComputers.LOGGER.warn("LOSING ENERGY! Tried giving $delta back to $charger and we're losing our marbles!")
}
}
}
}
// only call if storage
fun balanceStorage() {
for(battery in getReachable()) {
if(battery.powerRole != PowerRole.STORAGE) continue
// its so if for example we have a battery with 2x the capacity
// we don't try to even the energy between them since that's just bad
// and might pointless delete energy over time
val capacityRatio = energyCapacity.toDouble() / battery.energyCapacity
val meaningfulSurplus = (battery.energy * capacityRatio - energy).toLong()
if(meaningfulSurplus <= 0) {
// WE'RE greedy (or negligible surplus)? Do nothing
continue
}
// steal from this greedy mf
val toSteal = meaningfulSurplus / 2
if(toSteal == 0L) continue // broke storahh
val stolen = battery.withdrawEnergy(toSteal)
if(giveEnergy(stolen) < stolen) {
NeoComputers.LOGGER.warn("LOSING ENERGY IN NODE $this!!!! THIS IS REALLY BAD!!!")
}
}
}
// rob the generators
fun stealGeneratorPower() {
var remaining = energyCapacity - energy
for(generator in getReachable()) {
if(generator.powerRole != PowerRole.GENERATOR) continue
// rob this mf
val robbed = generator.withdrawEnergy(remaining)
val taken = giveEnergy(robbed)
if(taken < robbed) {
NeoComputers.LOGGER.warn("energy caught being DELETED in the big 26")
}
remaining -= taken
}
}
open fun tick() {
if(powerRole == PowerRole.CONSUMER) tryToChargeFully()
if(powerRole == PowerRole.STORAGE) {
stealGeneratorPower()
balanceStorage()
}
}
// processes a received message
open fun received(message: Message) {}
// called when a new direct connection is made
open fun onConnect(node: Node) {}
// called when a direct connection is lost
open fun onDisconnect(node: Node) {}
// called when a new node is added globally
open fun onNodeAdded(node: Node) {
reachableCache = null;
}
// called when a node is removed globally
open fun onNodeRemoved(node: Node) {
reachableCache = null;
}
fun getReachable(): Set<Node> {
if(reachableCache == null) {
reachableCache = computeReachable();
}
return reachableCache!!;
}
fun invalidateReachableCache() {
reachableCache = null
}
fun computeReachable(): Set<Node> {
if(reachability == Visibility.NONE) {
return setOf();
}
if(reachability == Visibility.DIRECT) {
return connections.minus(this);
}
if(reachability == Visibility.NETWORK) {
// absolute cinema
val working = HashSet<Node>();
val pending = mutableListOf(this);
var iterCount = 0;
while(iterCount < maxHopCount && pending.isNotEmpty()) {
iterCount++;
val subnode = pending.removeFirst();
if(subnode in working) continue;
working.add(subnode);
pending.addAll(subnode.connections);
}
// cannot send to itself!
working.remove(this);
return working;
}
throw NotImplementedError("visibility not implemented");
}
fun connectTo(other: Node) {
this.directConnectTo(other);
other.directConnectTo(this);
}
fun disconnectFrom(other: Node) {
this.directDisconnectFrom(other);
other.directDisconnectFrom(this);
}
fun directConnectTo(other: Node) {
if(other == this) return;
if(other in connections) return;
connections.add(other);
this.onConnect(other);
}
fun directDisconnectFrom(other: Node) {
if(other !in connections) return;
connections.remove(other);
this.onDisconnect(other);
}
}
abstract class WirelessEndpoint(address: UUID?) : Node(address) {
abstract fun getRange(): Double
abstract fun getDimension(): Int
abstract fun getPosition(): BlockPos
// separate from process for simplicity
abstract fun receiveWireless(message: Message, emitter: WirelessEndpoint)
}
class ComputerCheckedSignal(sender: DeviceNode, val player: String?, val name: String, val data: Array<Any>): Message(sender)
class ComputerUncheckedSignal(sender: DeviceNode, val name: String, val data: Array<Any>): Message(sender)
class ComputerEvent(sender: DeviceNode, val machineEvent: MachineEvent): Message(sender)
val wirelessNodes = ThreadLocal.withInitial { HashSet<WirelessEndpoint>() }
val allNodes = ThreadLocal.withInitial { HashMap<UUID, Node>() }
val allNodes = ThreadLocal.withInitial { HashMap<UUID, DeviceNode>() }
// node may differ from message.sender in the case of relays,
// as they might have DIRECT reachability but
fun emitMessage(node: Node, message: Message) {
node.getReachable().forEach { it.received(message) }
fun emitMessage(deviceNode: DeviceNode, message: Message) {
deviceNode.getReachable().forEach { it.received(message) }
}
fun computeRangeAllowedByHardness(src: BlockPos, dst: BlockPos): Double {
@@ -285,69 +82,69 @@ object Networking {
tickCount++
}
fun getNode(address: UUID): Node? = allNodes.get()[address]
fun getNode(address: UUID): DeviceNode? = allNodes.get()[address]
// TODO: use setter, more convenient
fun changeNodeAddress(node: Node, address: UUID) {
if(node.address.equals(address)) return
if(node.address !in allNodes.get()) return
allNodes.get().remove(node.address)
node.address = address
allNodes.get()[address] = node
fun changeNodeAddress(deviceNode: DeviceNode, address: UUID) {
if(deviceNode.address.equals(address)) return
if(deviceNode.address !in allNodes.get()) return
allNodes.get().remove(deviceNode.address)
deviceNode.address = address
allNodes.get()[address] = deviceNode
}
fun addNode(node: Node) {
if(node.address in allNodes.get()) return
allNodes.get()[node.address] = node
if(node is WirelessEndpoint) {
wirelessNodes.get().add(node);
fun addNode(deviceNode: DeviceNode) {
if(deviceNode.address in allNodes.get()) return
allNodes.get()[deviceNode.address] = deviceNode
if(deviceNode is WirelessEndpoint) {
wirelessNodes.get().add(deviceNode);
}
// notify at the end so it is notified of its own creation
allNodes.get().forEach { it.value.onNodeAdded(node) }
allNodes.get().forEach { it.value.onNodeAdded(deviceNode) }
}
fun addNodes(vararg nodes: Node) {
nodes.forEach { addNode(it) }
fun addNodes(vararg deviceNodes: DeviceNode) {
deviceNodes.forEach { addNode(it) }
}
fun removeNode(node: Node) {
if(node.address !in allNodes.get()) return
allNodes.get().forEach { it.value.onNodeRemoved(node) }
fun removeNode(deviceNode: DeviceNode) {
if(deviceNode.address !in allNodes.get()) return
allNodes.get().forEach { it.value.onNodeRemoved(deviceNode) }
// toList() in order to copy it
node.connections.toList().forEach {
node.disconnectFrom(it)
deviceNode.connections.toList().forEach {
deviceNode.disconnectFrom(it)
}
// actually remove at the end so it can listen to its own removal
allNodes.get().remove(node.address)
if(node is WirelessEndpoint) {
wirelessNodes.get().remove(node);
allNodes.get().remove(deviceNode.address)
if(deviceNode is WirelessEndpoint) {
wirelessNodes.get().remove(deviceNode);
}
}
fun removeNodes(vararg nodes: Node) {
nodes.forEach { removeNode(it) }
fun removeNodes(vararg deviceNodes: DeviceNode) {
deviceNodes.forEach { removeNode(it) }
}
val channels = ThreadLocal.withInitial { HashMap<String, MutableSet<Node>>() }
val channels = ThreadLocal.withInitial { HashMap<String, MutableSet<DeviceNode>>() }
fun addToChannel(channel: String, node: Node) {
fun addToChannel(channel: String, deviceNode: DeviceNode) {
val localChannels = channels.get()
if(!localChannels.containsKey(channel)) {
localChannels[channel] = mutableSetOf();
}
localChannels[channel]!!.add(node);
localChannels[channel]!!.add(deviceNode);
}
fun removeFromChannel(channel: String, node: Node) {
fun removeFromChannel(channel: String, deviceNode: DeviceNode) {
val localChannels = channels.get()
if(!localChannels.containsKey(channel)) return;
localChannels[channel]?.remove(node);
localChannels[channel]?.remove(deviceNode);
if(localChannels[channel].isNullOrEmpty()) {
localChannels.remove(channel);
}
}
fun emitChannelMessage(starter: Node, channel: String, message: Message) {
fun emitChannelMessage(starter: DeviceNode, channel: String, message: Message) {
val localChannels = channels.get()
val c = localChannels[channel] ?: return;
c.forEach { if(it != starter) it.received(message); };

View File

@@ -15,23 +15,23 @@ object PowerManager {
//? if fabric {
EnergyStorage.SIDED.registerForBlockEntity({
entity, dir -> object : EnergyStorage {
override fun getAmount() = entity.node.energy
override fun getCapacity() = entity.node.energyCapacity
override fun supportsExtraction() = entity.node.powerRole != PowerRole.CONSUMER && entity.node.energyCapacity > 0
override fun supportsInsertion() = entity.node.powerRole != PowerRole.GENERATOR
override fun getAmount() = entity.deviceNode.energy
override fun getCapacity() = entity.deviceNode.energyCapacity
override fun supportsExtraction() = entity.deviceNode.powerRole != PowerRole.CONSUMER && entity.deviceNode.energyCapacity > 0
override fun supportsInsertion() = entity.deviceNode.powerRole != PowerRole.GENERATOR
override fun extract(maxAmount: Long, transaction: TransactionContext?): Long {
if(entity.node.powerRole == PowerRole.CONSUMER) return 0
val taken = entity.node.withdrawEnergy(maxAmount)
if(entity.deviceNode.powerRole == PowerRole.CONSUMER) return 0
val taken = entity.deviceNode.withdrawEnergy(maxAmount)
transaction?.addCloseCallback {
ctx, res -> if(res.wasAborted() || !res.wasCommitted()) entity.node.giveEnergy(taken)
ctx, res -> if(res.wasAborted() || !res.wasCommitted()) entity.deviceNode.giveEnergy(taken)
}
return taken
}
override fun insert(maxAmount: Long, transaction: TransactionContext?): Long {
if(entity.node.powerRole == PowerRole.GENERATOR) return 0
val given = entity.node.giveEnergy(maxAmount)
if(entity.deviceNode.powerRole == PowerRole.GENERATOR) return 0
val given = entity.deviceNode.giveEnergy(maxAmount)
transaction?.addCloseCallback { ctx, res ->
if (res.wasAborted() || !res.wasCommitted()) entity.node.withdrawEnergy(given)
if (res.wasAborted() || !res.wasCommitted()) entity.deviceNode.withdrawEnergy(given)
}
return given
}