Skip to content

Commit

Permalink
add array, record and enum support to RecordSerializer, document the …
Browse files Browse the repository at this point in the history
…entire network api, make json groups append tabs rather than replace, add constructor-related methods in ReflectionUtils, add network test
  • Loading branch information
gliscowo committed Jan 19, 2022
1 parent f1c7b83 commit 569d592
Show file tree
Hide file tree
Showing 12 changed files with 650 additions and 84 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ minecraft_version=1.18.1
yarn_mappings=1.18.1+build.7
loader_version=0.12.12
# Mod Properties
mod_version=0.3.15
mod_version=0.3.15+pre2
maven_group=io.wispforest
archives_base_name=owo-lib
# Dependencies
Expand Down
17 changes: 11 additions & 6 deletions src/main/java/io/wispforest/owo/itemgroup/json/GroupTabLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ public String getDataSubdirectory() {
return "item_group_tabs";
}

@SuppressWarnings("ConstantConditions")
@Override
public void acceptParsedFile(Identifier id, JsonObject json) {
String targetGroup = JsonHelper.getString(json, "target_group");
Expand Down Expand Up @@ -87,12 +86,18 @@ public void acceptParsedFile(Identifier id, JsonObject json) {

for (ItemGroup group : ItemGroup.GROUPS) {
if (!group.getName().equals(targetGroup)) continue;
final var wrappedGroup = new WrapperGroup(group.getIndex(), group.getName(), createdTabs, createdButtons, group::createIcon);
wrappedGroup.initialize();

for (var item : Registry.ITEM) {
if (item.getGroup() != group) continue;
((OwoItemExtensions) item).setItemGroup(wrappedGroup);
if (group instanceof WrapperGroup wrapper) {
wrapper.addTabs(createdTabs);
wrapper.addButtons(createdButtons);
} else {
final var wrappedGroup = new WrapperGroup(group.getIndex(), group.getName(), createdTabs, createdButtons, group::createIcon);
wrappedGroup.initialize();

for (var item : Registry.ITEM) {
if (item.getGroup() != group) continue;
((OwoItemExtensions) item).setItemGroup(wrappedGroup);
}
}

return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import net.minecraft.item.ItemStack;
import org.jetbrains.annotations.ApiStatus;

import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;

Expand All @@ -30,6 +31,14 @@ public WrapperGroup(int index, String name, List<ItemGroupTab> tabs, List<ItemGr
this.icon = icon;
}

public void addTabs(Collection<ItemGroupTab> tabs) {
this.tabs.addAll(tabs);
}

public void addButtons(Collection<ItemGroupButton> buttons) {
this.buttons.addAll(buttons);
}

@Override
protected void setup() {}

Expand Down
205 changes: 204 additions & 1 deletion src/main/java/io/wispforest/owo/network/OwoNetChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,45 @@
import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;

import java.util.*;

import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
* An efficient networking abstraction that uses {@code record}s to store
* and define packet data. Serialization for most types is fully automatic
* and no custom handling needs to be done, should one of your record
* components be of an unsupported type use {@link io.wispforest.owo.network.serialization.TypeAdapter#register(Class, BiConsumer, Function)}
* to register a custom serializer.
*
* <p> To define a packet class suited for use with this wrapper, simply create a
* standard Java {@code record} class and put the desired data into the record header.
*
* <p>To register a packet onto this channel, use either {@link #registerClientbound(Class, ChannelHandler)}
* or {@link #registerServerbound(Class, ChannelHandler)}, depending on which direction the packet goes.
* Bidirectional registration of the same class is explicitly supported. <b>For synchronization purposes,
* all registration must happen on both client and server, even for clientbound packets.</b>
*
* <p>To send a packet, use any of the {@code handle} methods to obtain a handle for sending. These are
* named after where the packet is sent <i>from</i>, meaning the {@link #clientHandle()} is used for sending
* <i>to the server</i> and vice-versa.
*
* <p> The registered packet handlers are executed synchronously on the target environment's
* game thread instead of Netty's event loops - there is no need to call {@code .execute(...)}
*
* @see io.wispforest.owo.network.serialization.TypeAdapter#register(Class, BiConsumer, Function)
* @see io.wispforest.owo.network.serialization.TypeAdapter#registerCollectionProvider(Class, Supplier)
*/
public class OwoNetChannel {

private static final Map<Identifier, String> REGISTERED_CHANNELS = new HashMap<>();
Expand All @@ -33,6 +64,16 @@ public class OwoNetChannel {
private ClientHandle clientHandle = null;
private ServerHandle serverHandle = null;

/**
* Creates a new channel with given ID. Duplicate channel IDs
* are not allowed - if there is a collision, the name of the
* class that previously registered the channel will be part of
* the exception. <b>This may be called at any stage during
* mod initialization</b>
*
* @param id The desired channel ID
* @return The created channel
*/
public static OwoNetChannel create(Identifier id) {
return new OwoNetChannel(id, ReflectionUtils.getCallingClassName(2));
}
Expand Down Expand Up @@ -63,20 +104,48 @@ private OwoNetChannel(Identifier id, String ownerClassName) {
REGISTERED_CHANNELS.put(id, ownerClassName);
}

/**
* Registers a handler <i>on the client</i> for the specified message class.
* This also ensures the required serializer is available. If an exception
* about a missing type adapter is thrown, register one
*
* @param messageClass The type of packet data to send and serialize
* @param handler The handler that will receive the deserialized
* @see #serverHandle(PlayerEntity)
* @see #serverHandle(MinecraftServer)
* @see #serverHandle(ServerWorld, BlockPos)
* @see io.wispforest.owo.network.serialization.TypeAdapter#register(Class, BiConsumer, Function)
*/
@SuppressWarnings("unchecked")
public <R extends Record> void registerClientbound(Class<R> messageClass, ChannelHandler<R, ClientAccess> handler) {
int index = this.clientHandlers.size();
this.createSerializer(messageClass, index, EnvType.CLIENT);
this.clientHandlers.add((ChannelHandler<Record, ClientAccess>) handler);
}

/**
* Registers a handler <i>on the server</i> for the specified message class.
* This also ensures the required serializer is available. If an exception
* about a missing type adapter is thrown, register one
*
* @param messageClass The type of packet data to send and serialize
* @param handler The handler that will receive the deserialized
* @see #clientHandle()
* @see io.wispforest.owo.network.serialization.TypeAdapter#register(Class, BiConsumer, Function)
*/
@SuppressWarnings("unchecked")
public <R extends Record> void registerServerbound(Class<R> messageClass, ChannelHandler<R, ServerAccess> handler) {
int index = this.serverHandlers.size();
this.createSerializer(messageClass, index, EnvType.SERVER);
this.serverHandlers.add((ChannelHandler<Record, ServerAccess>) handler);
}

/**
* Obtains the client handle of this channel, used to
* send packets <i>to the server</i>
*
* @return The client handle of this channel
*/
public ClientHandle clientHandle() {
if (FabricLoader.getInstance().getEnvironmentType() != EnvType.CLIENT)
throw new NetworkException("Cannot obtain client handle in environment type '" + FabricLoader.getInstance().getEnvironmentType() + "'");
Expand All @@ -85,18 +154,50 @@ public ClientHandle clientHandle() {
return clientHandle;
}

/**
* Obtains a server handle used to send packets
* <i>to all players on the given server</i>
* <p>
* <b>This handle will be reused - do not retain references</b>
*
* @param server The server to target
* @return A server handle configured for sending packets
* to all players on the given server
*/
public ServerHandle serverHandle(MinecraftServer server) {
var handle = getServerHandle();
handle.targets = PlayerLookup.all(server);
return handle;
}

/**
* Obtains a server handle used to send packets
* <i>to all given players</i>. Use {@link PlayerLookup} to obtain
* the required collections
* <p>
* <b>This handle will be reused - do not retain references</b>
*
* @param targets The players to target
* @return A server handle configured for sending packets
* to all players in the given collection
* @see PlayerLookup
*/
public ServerHandle serverHandle(Collection<ServerPlayerEntity> targets) {
var handle = getServerHandle();
handle.targets = targets;
return handle;
}

/**
* Obtains a server handle used to send packets
* <i>to the given player only</i>
* <p>
* <b>This handle will be reused - do not retain references</b>
*
* @param player The player to target
* @return A server handle configured for sending packets
* to the given player only
*/
public ServerHandle serverHandle(PlayerEntity player) {
if (!(player instanceof ServerPlayerEntity serverPlayer)) throw new NetworkException("'player' must be a 'ServerPlayerEntity'");

Expand All @@ -105,6 +206,36 @@ public ServerHandle serverHandle(PlayerEntity player) {
return handle;
}

/**
* Obtains a server handle used to send packets
* <i>to all players tracking the given block entity</i>
* <p>
* <b>This handle will be reused - do not retain references</b>
*
* @param entity The block entity to look up trackers for
* @return A server handle configured for sending packets
* to all players tracking the given block entity
*/
public ServerHandle serverHandle(BlockEntity entity) {
if (entity.getWorld().isClient) throw new NetworkException("Server handle cannot be obtained on the client");
return serverHandle(PlayerLookup.tracking(entity));
}

/**
* Obtains a server handle used to send packets <i>to all
* players tracking the given position in the given world</i>
* <p>
* <b>This handle will be reused - do not retain references</b>
*
* @param world The world to look up players in
* @param pos The position to look up trackers for
* @return A server handle configured for sending packets
* to all players tracking the given position in the given world
*/
public ServerHandle serverHandle(ServerWorld world, BlockPos pos) {
return serverHandle(PlayerLookup.tracking(world, pos));
}

private ServerHandle getServerHandle() {
if (this.serverHandle == null) this.serverHandle = new ServerHandle();
return serverHandle;
Expand Down Expand Up @@ -145,30 +276,102 @@ private <R extends Record> PacketByteBuf encode(R message, EnvType target) {
}

public class ClientHandle {

/**
* Sends the given message to the server
*
* @param message The message to send
* @see #send(Record[])
*/
public <R extends Record> void send(R message) {
ClientPlayNetworking.send(OwoNetChannel.this.packetId, OwoNetChannel.this.encode(message, EnvType.SERVER));
}

/**
* Sends the given messages to the server
*
* @param messages The messages to send
*/
@SafeVarargs
public final <R extends Record> void send(R... messages) {
for (R message : messages) send(message);
}
}

public class ServerHandle {

private Collection<ServerPlayerEntity> targets = Collections.emptySet();

/**
* Sends the given message to the configured target(s)
* <b>Resets the target(s) after sending - this cannot be used
* for multiple messages on the same handle</b>
*
* @param message The message to send
* @see #send(Record[])
*/
public <R extends Record> void send(R message) {
this.targets.forEach(player -> ServerPlayNetworking.send(player, OwoNetChannel.this.packetId, OwoNetChannel.this.encode(message, EnvType.CLIENT)));
this.targets = null;
}

/**
* Sends the given messages to the configured target(s)
* <b>Resets the target(s) after sending - this cannot be used
* multiple times on the same handle</b>
*
* @param messages The messages to send
*/
@SafeVarargs
public final <R extends Record> void send(R... messages) {
this.targets.forEach(player -> {
for (R message : messages) {
ServerPlayNetworking.send(player, OwoNetChannel.this.packetId, OwoNetChannel.this.encode(message, EnvType.CLIENT));
}
});
this.targets = null;
}
}

public interface ChannelHandler<R extends Record, E extends EnvironmentAccess<?, ?, ?>> {

/**
* Executed on the game thread to handle the incoming
* message - this can safely modify game state
*
* @param message The message that was received
* @param access The {@link EnvironmentAccess} used to obtain references
* to the execution environment
*/
void handle(R message, E access);
}

/**
* A simple wrapper that provides access to the environment a packet
* is being received / message is being handled in
*
* @param <P> The type of player to receive the packet
* @param <R> The runtime that the packet is being received in
* @param <N> The network handler that received the packet
*/
public interface EnvironmentAccess<P extends PlayerEntity, R, N> {

/**
* @return The player that received the packet
*/
P player();

/**
* @return The environment the packet is being received in,
* either a {@link MinecraftServer} or a {@link net.minecraft.client.MinecraftClient}
*/
R runtime();

/**
* @return The network handler of the player or client that received the packet,
* either a {@link net.minecraft.client.network.ClientPlayNetworkHandler} or a
* {@link net.minecraft.server.network.ServerPlayNetworkHandler}
*/
N netHandler();
}

Expand Down
Loading

0 comments on commit 569d592

Please sign in to comment.