Rudimentary network implementation

This commit is contained in:
Shadowfacts 2019-10-26 21:36:31 -04:00
parent 7b1a82d02b
commit 0eabbf9ec1
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
20 changed files with 619 additions and 0 deletions

View File

@ -0,0 +1,7 @@
package net.shadowfacts.phycon.api;
/**
* @author shadowfacts
*/
public interface NetworkCable {
}

View File

@ -0,0 +1,14 @@
package net.shadowfacts.phycon.api;
import net.minecraft.item.ItemStack;
import java.util.Map;
/**
* @author shadowfacts
*/
public interface NetworkInterface extends PacketSink {
Map<ItemStack, Integer> readAll();
}

View File

@ -0,0 +1,13 @@
package net.shadowfacts.phycon.api;
import net.shadowfacts.phycon.api.packet.Packet;
import org.jetbrains.annotations.NotNull;
/**
* @author shadowfacts
*/
public interface PacketSink {
void handle(@NotNull Packet packet);
}

View File

@ -0,0 +1,14 @@
package net.shadowfacts.phycon.api;
import alexiil.mc.lib.attributes.Attribute;
import alexiil.mc.lib.attributes.Attributes;
/**
* @author shadowfacts
*/
public class PhyAttributes {
public static final Attribute<PacketSink> PACKET_SINK = Attributes.create(PacketSink.class);
public static final Attribute<NetworkInterface> NETWORK_INTERFACE = Attributes.create(NetworkInterface.class);
}

View File

@ -0,0 +1,17 @@
package net.shadowfacts.phycon.api.packet;
import net.shadowfacts.phycon.api.util.MACAddress;
import org.jetbrains.annotations.NotNull;
/**
* @author shadowfacts
*/
public interface Packet {
@NotNull
MACAddress getSource();
@NotNull
MACAddress getDestination();
}

View File

@ -0,0 +1,84 @@
package net.shadowfacts.phycon.api.util;
import org.jetbrains.annotations.NotNull;
import java.util.Random;
/**
* @author shadowfacts
*/
public final class MACAddress {
public enum Type {
BROADCAST, UNICAST, MULTICAST;
}
public static final MACAddress BROADCAST = new MACAddress(0xffffffffffffL);
public static MACAddress random(Random random) {
long value = random.nextLong() & 0xfeffffffffffL;
return new MACAddress(value);
}
private static final Random macAddressRandom = new Random();
public static MACAddress random() {
return random(macAddressRandom);
}
public final long address;
public MACAddress(long address) {
if (address != (address & 0xffffffffffffL)) {
throw new RuntimeException("Invalid MAC address, must be 48 bits.");
}
this.address = address;
}
@NotNull
public MACAddress toUnicast() {
return new MACAddress(address & 0xfeffffffffffL);
}
@NotNull
public MACAddress toMulticast() {
return new MACAddress(address | 0x010000000000L);
}
@NotNull
public Type getType() {
if (this == BROADCAST) {
return Type.BROADCAST;
} else if (((address >> 46) & 1) == 1) {
return Type.MULTICAST;
} else {
return Type.UNICAST;
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MACAddress that = (MACAddress)o;
return address == that.address;
}
@Override
public int hashCode() {
return Long.hashCode(address);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(17);
for (int i = 5; i >= 0; i--) {
if (i < 5) {
builder.append(':');
}
long octet = (address >> (i * 8)) & 0xff;
builder.append(Long.toHexString(octet));
}
return builder.toString();
}
}

View File

@ -0,0 +1,20 @@
package net.shadowfacts.phycon
import net.fabricmc.api.ModInitializer
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyItems
/**
* @author shadowfacts
*/
object PhysicalConnectivity: ModInitializer {
val MODID = "phycon"
override fun onInitialize() {
PhyBlocks.init()
PhyBlockEntities.init()
PhyItems.init()
}
}

View File

@ -0,0 +1,22 @@
package net.shadowfacts.phycon.block
import net.minecraft.block.Block
import net.minecraft.block.BlockEntityProvider
import net.minecraft.block.entity.BlockEntity
import net.minecraft.util.math.BlockPos
import net.minecraft.world.BlockView
/**
* @author shadowfacts
*/
abstract class BlockWithEntity<T: BlockEntity>(settings: Settings): Block(settings), BlockEntityProvider {
abstract override fun createBlockEntity(world: BlockView): T?
fun getBlockEntity(world: BlockView, pos: BlockPos): T? {
val entity = world.getBlockEntity(pos)
return if (entity != null) {
entity as? T
} else {
null
}
}
}

View File

@ -0,0 +1,34 @@
package net.shadowfacts.phycon.init
import net.minecraft.block.Block
import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityType
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.network.block.networkinterface.NetworkInterfaceBlock
import net.shadowfacts.phycon.network.block.networkinterface.NetworkInterfaceBlockEntity
import net.shadowfacts.phycon.network.block.terminal.TerminalBlock
import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity
/**
* @author shadowfacts
*/
object PhyBlockEntities {
val NETWORK_INTERFACE = create(::NetworkInterfaceBlockEntity, PhyBlocks.NETWORK_INTERFACE)
val TERMINAL = create(::TerminalBlockEntity, PhyBlocks.TERMINAL)
private fun <T: BlockEntity> create(builder: () -> T, block: Block): BlockEntityType<T> {
return BlockEntityType.Builder.create(builder, arrayOf(block)).build(null)
}
fun init() {
register(NetworkInterfaceBlock.ID, NETWORK_INTERFACE)
register(TerminalBlock.ID, TERMINAL)
}
private fun register(id: Identifier, type: BlockEntityType<*>) {
Registry.register(Registry.BLOCK_ENTITY, id, type)
}
}

View File

@ -0,0 +1,26 @@
package net.shadowfacts.phycon.init
import net.minecraft.block.Block
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.network.block.networkinterface.NetworkInterfaceBlock
import net.shadowfacts.phycon.network.block.terminal.TerminalBlock
/**
* @author shadowfacts
*/
object PhyBlocks {
val NETWORK_INTERFACE = NetworkInterfaceBlock()
val TERMINAL = TerminalBlock()
fun init() {
register(NetworkInterfaceBlock.ID, NETWORK_INTERFACE)
register(TerminalBlock.ID, TERMINAL)
}
private fun register(id: Identifier, block: Block) {
Registry.register(Registry.BLOCK, id, block)
}
}

View File

@ -0,0 +1,27 @@
package net.shadowfacts.phycon.init
import net.minecraft.item.BlockItem
import net.minecraft.item.Item
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.network.block.networkinterface.NetworkInterfaceBlock
import net.shadowfacts.phycon.network.block.terminal.TerminalBlock
/**
* @author shadowfacts
*/
object PhyItems {
val NETWORK_INTERFACE = BlockItem(PhyBlocks.NETWORK_INTERFACE, Item.Settings())
val TERMINAL = BlockItem(PhyBlocks.TERMINAL, Item.Settings())
fun init() {
register(NetworkInterfaceBlock.ID, NETWORK_INTERFACE)
register(TerminalBlock.ID, TERMINAL)
}
private fun register(id: Identifier, item: Item) {
Registry.register(Registry.ITEM, id, item)
}
}

View File

@ -0,0 +1,66 @@
package net.shadowfacts.phycon.network
import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityType
import net.minecraft.nbt.CompoundTag
import net.minecraft.util.Tickable
import net.shadowfacts.phycon.api.PacketSink
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.MACAddress
import java.util.*
/**
* @author shadowfacts
*/
abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type), Tickable, PacketSink {
var macAddress = MACAddress.random()
protected set
private val sendQueue = LinkedList<Packet>()
override fun handle(packet: Packet) {
if (acceptsPacket(packet)) {
handlePacket(packet)
}
}
abstract fun handlePacket(packet: Packet)
fun acceptsPacket(packet: Packet): Boolean {
return when (packet.destination.type) {
MACAddress.Type.BROADCAST -> true
MACAddress.Type.UNICAST -> macAddress == packet.destination
MACAddress.Type.MULTICAST -> acceptsMulticastPacket(packet)
}
}
fun acceptsMulticastPacket(packet: Packet): Boolean {
return false
}
fun enqueue(packet: Packet) {
sendQueue.add(packet)
}
override fun tick() {
if (sendQueue.isNotEmpty()) {
val packet = sendQueue.pop()
val destinations = NetworkUtil.findDestinations(world!!, pos)
destinations.forEach {
it.handle(packet)
}
}
}
override fun toTag(tag: CompoundTag): CompoundTag {
tag.putLong("MACAddress", macAddress.address)
return super.toTag(tag)
}
override fun fromTag(tag: CompoundTag) {
super.fromTag(tag)
macAddress = MACAddress(tag.getLong("MACAddress"))
}
}

View File

@ -0,0 +1,44 @@
package net.shadowfacts.phycon.network
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.World
import net.shadowfacts.phycon.api.NetworkCable
import net.shadowfacts.phycon.api.PacketSink
import net.shadowfacts.phycon.api.PhyAttributes
import java.util.*
/**
* @author shadowfacts
*/
object NetworkUtil {
fun findDestinations(world: World, startPos: BlockPos): List<PacketSink> {
val results = mutableListOf<PacketSink>()
val visited = hashSetOf(startPos)
val queue = LinkedList<BlockPos>()
addAdjacent(queue, visited, startPos)
while (queue.isNotEmpty()) {
val pos = queue.pop()
val sink = PhyAttributes.PACKET_SINK.getFirstOrNull(world, pos)
if (sink != null) {
results.add(sink)
}
if (pos === startPos || world.getBlockState(pos).block is NetworkCable) {
addAdjacent(queue, visited, pos)
}
visited.add(pos)
}
return results
}
fun addAdjacent(queue: MutableList<BlockPos>, visited: Set<BlockPos>, pos: BlockPos) {
for (dir in Direction.values()) {
val newPos = pos.offset(dir)
if (newPos !in visited) {
queue.add(newPos)
}
}
}
}

View File

@ -0,0 +1,67 @@
package net.shadowfacts.phycon.network.block.networkinterface
import alexiil.mc.lib.attributes.AttributeList
import alexiil.mc.lib.attributes.AttributeProvider
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.Material
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack
import net.minecraft.state.StateFactory
import net.minecraft.state.property.Properties
import net.minecraft.util.Hand
import net.minecraft.util.Identifier
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.block.BlockWithEntity
/**
* @author shadowfacts
*/
class NetworkInterfaceBlock: BlockWithEntity<NetworkInterfaceBlockEntity>(Settings.of(Material.METAL)), AttributeProvider {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "network_interface")
val FACING = Properties.FACING
}
override fun appendProperties(builder: StateFactory.Builder<Block, BlockState>) {
super.appendProperties(builder)
builder.add(FACING)
}
override fun createBlockEntity(world: BlockView) = NetworkInterfaceBlockEntity()
override fun getPlacementState(context: ItemPlacementContext): BlockState {
val facing = if (context.player?.isSneaking == true) context.playerFacing else context.playerFacing.opposite
return defaultState.with(FACING, facing)
}
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, placer: LivingEntity?, stack: ItemStack) {
if (!world.isClient) {
getBlockEntity(world, pos)!!.updateInventory()
}
}
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighborBlock: Block, neighborPos: BlockPos, boolean_1: Boolean) {
if (!world.isClient) {
getBlockEntity(world, pos)!!.updateInventory()
}
}
override fun activate(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, result: BlockHitResult): Boolean {
if (!world.isClient) {
getBlockEntity(world, pos)!!.printContents()
}
return true
}
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
to.offer(getBlockEntity(world, pos))
}
}

View File

@ -0,0 +1,54 @@
package net.shadowfacts.phycon.network.block.networkinterface
import alexiil.mc.lib.attributes.SearchOptions
import alexiil.mc.lib.attributes.item.GroupedItemInv
import alexiil.mc.lib.attributes.item.ItemAttributes
import net.minecraft.item.ItemStack
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.packet.ReadAllPacket
import net.shadowfacts.phycon.network.packet.RequestReadAllPacket
/**
* @author shadowfacts
*/
class NetworkInterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.NETWORK_INTERFACE) {
private val facing: Direction
get() = world!!.getBlockState(pos)[NetworkInterfaceBlock.FACING]
private var inventory: GroupedItemInv? = null
fun updateInventory() {
val offsetPos = pos.offset(facing)
val option = SearchOptions.inDirection(facing)
inventory = ItemAttributes.GROUPED_INV.getFirstOrNull(world, offsetPos, option)
}
fun printContents() {
inventory?.also { inv ->
inv.storedStacks.forEach {
println(it)
}
}
}
override fun handlePacket(packet: Packet) {
when (packet) {
is RequestReadAllPacket -> handleReadAll(packet)
}
}
fun handleReadAll(packet: RequestReadAllPacket) {
enqueue(ReadAllPacket(readAll(), macAddress, packet.source))
}
fun readAll(): Map<ItemStack, Int> {
return inventory?.let {
it.storedStacks.associateWith(it::getAmount)
} ?: mapOf()
}
}

View File

@ -0,0 +1,35 @@
package net.shadowfacts.phycon.network.block.terminal
import alexiil.mc.lib.attributes.AttributeList
import alexiil.mc.lib.attributes.AttributeProvider
import net.minecraft.block.BlockState
import net.minecraft.block.Material
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.util.Hand
import net.minecraft.util.Identifier
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.block.BlockWithEntity
/**
* @author shadowfacts
*/
class TerminalBlock: BlockWithEntity<TerminalBlockEntity>(Settings.of(Material.METAL)), AttributeProvider {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "terminal")
}
override fun createBlockEntity(world: BlockView) = TerminalBlockEntity()
override fun activate(blockState_1: BlockState?, world_1: World?, blockPos_1: BlockPos?, playerEntity_1: PlayerEntity?, hand_1: Hand?, blockHitResult_1: BlockHitResult?): Boolean {
getBlockEntity(world_1!!, blockPos_1!!)!!.onActivate()
return true
}
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
to.offer(getBlockEntity(world, pos))
}
}

View File

@ -0,0 +1,37 @@
package net.shadowfacts.phycon.network.block.terminal
import net.minecraft.item.ItemStack
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.packet.ReadAllPacket
import net.shadowfacts.phycon.network.packet.RequestReadAllPacket
/**
* @author shadowfacts
*/
class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL) {
private var cachedItems = mutableMapOf<ItemStack, Int>()
override fun handlePacket(packet: Packet) {
when (packet) {
is ReadAllPacket -> handleReadAll(packet)
}
}
fun handleReadAll(packet: ReadAllPacket) {
packet.items.forEach { (stack, amount) ->
cachedItems.merge(stack, amount) { a, b -> a + b }
}
println("new cached items: $cachedItems")
}
fun onActivate() {
if (!world!!.isClient) {
cachedItems.clear()
enqueue(RequestReadAllPacket(macAddress))
}
}
}

View File

@ -0,0 +1,17 @@
package net.shadowfacts.phycon.network.packet
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.MACAddress
/**
* @author shadowfacts
*/
abstract class BasePacket(
@JvmField private val source: MACAddress,
@JvmField private val destination: MACAddress
): Packet {
override fun getSource() = source
override fun getDestination() = destination
}

View File

@ -0,0 +1,13 @@
package net.shadowfacts.phycon.network.packet
import net.minecraft.item.ItemStack
import net.shadowfacts.phycon.api.util.MACAddress
/**
* @author shadowfacts
*/
class ReadAllPacket(
val items: Map<ItemStack, Int>,
source: MACAddress,
destination: MACAddress
): BasePacket(source, destination)

View File

@ -0,0 +1,8 @@
package net.shadowfacts.phycon.network.packet
import net.shadowfacts.phycon.api.util.MACAddress
/**
* @author shadowfacts
*/
class RequestReadAllPacket(source: MACAddress, destination: MACAddress = MACAddress.BROADCAST): BasePacket(source, destination)