From a9d2a7f6c5b8b5489e7d15e44f0f3ed7f741a261 Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Mon, 28 Sep 2020 22:39:01 +0300 Subject: [PATCH 1/2] Added stateful objects Also added entity logic, moved test content to a separate package, changed ImplementedChangeTracker a bit, added PacketEntityChange, made entity IDs longs instead of UUIDs, and changed a whole bunch of minor stuff. Damn, this took CENTURIES to write. And it's still incomplete and crap. Like, we've got object allocation all over the place in the tick cycle, stateful changes are potentially super inefficient and we might have concurrency problems down the line. AND I'll still have to write 10000 primitive wrappers. Urgh. I don't even feel like writing a coherent commit message. Screw you, potential future reader of this commit message. --- .../progressia/client/ClientProxy.java | 1 + .../comms/DefaultClientCommsListener.java | 31 +-- .../comms/packets/PacketSetLocalPlayer.java | 16 +- .../state/AbstractStatefulObjectLayout.java | 83 +++++++ .../common/state/HashMapStateStorage.java | 20 ++ .../progressia/common/state/IOContext.java | 9 + .../state/InspectingStatefulObjectLayout.java | 103 ++++++++ .../common/state/IntStateField.java | 62 +++++ .../common/state/OptimizedStateStorage.java | 19 ++ .../state/OptimizedStatefulObjectLayout.java | 68 ++++++ .../common/state/PrimitiveCounters.java | 21 ++ .../progressia/common/state/StateChanger.java | 7 + .../progressia/common/state/StateField.java | 50 ++++ .../common/state/StateFieldBuilder.java | 24 ++ .../progressia/common/state/StateStorage.java | 9 + .../common/state/StatefulObject.java | 224 ++++++++++++++++++ .../common/state/StatefulObjectLayout.java | 48 ++++ .../common/state/StatefulObjectRegistry.java | 98 ++++++++ .../common/util/CoordinatePacker.java | 30 +++ .../progressia/common/util/DataBuffer.java | 121 ++++++++++ .../common/util/NamespacedRegistry.java | 3 + .../common/util/NamespacedUtil.java | 46 ++++ .../progressia/common/world/ChunkData.java | 11 +- .../progressia/common/world/WorldData.java | 49 +++- .../common/world/entity/EntityData.java | 32 ++- .../world/entity/EntityDataRegistry.java | 8 +- .../world/entity/PacketEntityChange.java | 60 +++++ .../progressia/server/ServerThread.java | 10 +- .../server/comms/ClientManager.java | 6 +- .../progressia/server/world/Changer.java | 8 + .../world/ImplementedChangeTracker.java | 77 +++++- .../progressia/server/world/Ticker.java | 22 ++ .../server/world/entity/EntityLogic.java | 7 + .../{client => test}/TestContent.java | 22 +- .../progressia/test/TestEntityDataStatie.java | 24 ++ .../test/TestEntityLogicStatie.java | 24 ++ .../TestEntityRenderJavapony.java | 4 +- .../test/TestEntityRenderStatie.java | 42 ++++ 38 files changed, 1431 insertions(+), 68 deletions(-) create mode 100644 src/main/java/ru/windcorp/progressia/common/state/AbstractStatefulObjectLayout.java create mode 100644 src/main/java/ru/windcorp/progressia/common/state/HashMapStateStorage.java create mode 100644 src/main/java/ru/windcorp/progressia/common/state/IOContext.java create mode 100644 src/main/java/ru/windcorp/progressia/common/state/InspectingStatefulObjectLayout.java create mode 100644 src/main/java/ru/windcorp/progressia/common/state/IntStateField.java create mode 100644 src/main/java/ru/windcorp/progressia/common/state/OptimizedStateStorage.java create mode 100644 src/main/java/ru/windcorp/progressia/common/state/OptimizedStatefulObjectLayout.java create mode 100644 src/main/java/ru/windcorp/progressia/common/state/PrimitiveCounters.java create mode 100644 src/main/java/ru/windcorp/progressia/common/state/StateChanger.java create mode 100644 src/main/java/ru/windcorp/progressia/common/state/StateField.java create mode 100644 src/main/java/ru/windcorp/progressia/common/state/StateFieldBuilder.java create mode 100644 src/main/java/ru/windcorp/progressia/common/state/StateStorage.java create mode 100644 src/main/java/ru/windcorp/progressia/common/state/StatefulObject.java create mode 100644 src/main/java/ru/windcorp/progressia/common/state/StatefulObjectLayout.java create mode 100644 src/main/java/ru/windcorp/progressia/common/state/StatefulObjectRegistry.java create mode 100644 src/main/java/ru/windcorp/progressia/common/util/DataBuffer.java create mode 100644 src/main/java/ru/windcorp/progressia/common/util/NamespacedUtil.java create mode 100644 src/main/java/ru/windcorp/progressia/common/world/entity/PacketEntityChange.java rename src/main/java/ru/windcorp/progressia/{client => test}/TestContent.java (86%) create mode 100644 src/main/java/ru/windcorp/progressia/test/TestEntityDataStatie.java create mode 100644 src/main/java/ru/windcorp/progressia/test/TestEntityLogicStatie.java rename src/main/java/ru/windcorp/progressia/{client => test}/TestEntityRenderJavapony.java (95%) create mode 100644 src/main/java/ru/windcorp/progressia/test/TestEntityRenderStatie.java diff --git a/src/main/java/ru/windcorp/progressia/client/ClientProxy.java b/src/main/java/ru/windcorp/progressia/client/ClientProxy.java index caa6f52..9690642 100644 --- a/src/main/java/ru/windcorp/progressia/client/ClientProxy.java +++ b/src/main/java/ru/windcorp/progressia/client/ClientProxy.java @@ -27,6 +27,7 @@ import ru.windcorp.progressia.client.graphics.texture.Atlases; import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; import ru.windcorp.progressia.common.resource.ResourceManager; import ru.windcorp.progressia.server.ServerState; +import ru.windcorp.progressia.test.TestContent; public class ClientProxy implements Proxy { diff --git a/src/main/java/ru/windcorp/progressia/client/comms/DefaultClientCommsListener.java b/src/main/java/ru/windcorp/progressia/client/comms/DefaultClientCommsListener.java index 299c50a..4292bb9 100644 --- a/src/main/java/ru/windcorp/progressia/client/comms/DefaultClientCommsListener.java +++ b/src/main/java/ru/windcorp/progressia/client/comms/DefaultClientCommsListener.java @@ -1,9 +1,6 @@ package ru.windcorp.progressia.client.comms; import java.io.IOException; -import java.util.Collection; -import java.util.UUID; - import ru.windcorp.progressia.client.Client; import ru.windcorp.progressia.client.graphics.world.EntityAnchor; import ru.windcorp.progressia.client.world.ChunkRender; @@ -11,9 +8,10 @@ import ru.windcorp.progressia.common.comms.CommsListener; import ru.windcorp.progressia.common.comms.packets.Packet; import ru.windcorp.progressia.common.comms.packets.PacketSetLocalPlayer; import ru.windcorp.progressia.common.comms.packets.PacketWorldChange; -import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.common.world.entity.PacketEntityChange; +// TODO refactor with no mercy public class DefaultClientCommsListener implements CommsListener { private final Client client; @@ -29,31 +27,18 @@ public class DefaultClientCommsListener implements CommsListener { getClient().getWorld().getData() ); - tmp_reassembleWorld(); + if (!(packet instanceof PacketEntityChange)) { + tmp_reassembleWorld(); + } } else if (packet instanceof PacketSetLocalPlayer) { setLocalPlayer((PacketSetLocalPlayer) packet); } } private void setLocalPlayer(PacketSetLocalPlayer packet) { - UUID uuid = packet.getLocalPlayerEntityUUID(); - - Collection chunks = - getClient().getWorld().getData().getChunks(); - - EntityData entity = null; - - synchronized (chunks) { - chunkLoop: - for (ChunkData chunk : chunks) { - for (EntityData anEntity : chunk.getEntities()) { - if (anEntity.getUUID().equals(uuid)) { - entity = anEntity; - break chunkLoop; - } - } - } - } + EntityData entity = getClient().getWorld().getData().getEntity( + packet.getLocalPlayerEntityId() + ); if (entity == null) { throw new RuntimeException(""); diff --git a/src/main/java/ru/windcorp/progressia/common/comms/packets/PacketSetLocalPlayer.java b/src/main/java/ru/windcorp/progressia/common/comms/packets/PacketSetLocalPlayer.java index f20843a..4e12d23 100644 --- a/src/main/java/ru/windcorp/progressia/common/comms/packets/PacketSetLocalPlayer.java +++ b/src/main/java/ru/windcorp/progressia/common/comms/packets/PacketSetLocalPlayer.java @@ -1,18 +1,20 @@ package ru.windcorp.progressia.common.comms.packets; -import java.util.UUID; - public class PacketSetLocalPlayer extends Packet { - private final UUID localPlayerEntityUUID; + private long localPlayerEntityId; - public PacketSetLocalPlayer(UUID uuid) { + public PacketSetLocalPlayer(long entityId) { super("Core", "SetLocalPlayer"); - this.localPlayerEntityUUID = uuid; + this.localPlayerEntityId = entityId; } - public UUID getLocalPlayerEntityUUID() { - return localPlayerEntityUUID; + public long getLocalPlayerEntityId() { + return localPlayerEntityId; + } + + public void setLocalPlayerEntityId(long localPlayerEntityId) { + this.localPlayerEntityId = localPlayerEntityId; } } diff --git a/src/main/java/ru/windcorp/progressia/common/state/AbstractStatefulObjectLayout.java b/src/main/java/ru/windcorp/progressia/common/state/AbstractStatefulObjectLayout.java new file mode 100644 index 0000000..3a19e04 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/state/AbstractStatefulObjectLayout.java @@ -0,0 +1,83 @@ +package ru.windcorp.progressia.common.state; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public abstract class AbstractStatefulObjectLayout +extends StatefulObjectLayout { + + public AbstractStatefulObjectLayout(String objectId) { + super(objectId); + } + + protected abstract int getFieldCount(); + protected abstract StateField getField(int fieldIndex); + + @Override + public void read( + StatefulObject object, + DataInput input, + IOContext context + ) throws IOException { + + int fieldCount = getFieldCount(); + for (int i = 0; i < fieldCount; ++i) { + + StateField field = getField(i); + if (context == IOContext.COMMS && field.isLocal()) continue; + field.read(object, input, context); + + } + } + + @Override + public void write( + StatefulObject object, + DataOutput output, + IOContext context + ) throws IOException { + + int fieldCount = getFieldCount(); + for (int i = 0; i < fieldCount; ++i) { + + StateField field = getField(i); + if (context == IOContext.COMMS && field.isLocal()) continue; + field.write(object, output, context); + + } + } + + @Override + public void copy(StatefulObject from, StatefulObject to) { + int fieldCount = getFieldCount(); + for (int i = 0; i < fieldCount; ++i) { + getField(i).copy(from, to); + } + } + + @Override + public int computeHashCode(StatefulObject object) { + int result = 1; + + int fieldCount = getFieldCount(); + for (int i = 0; i < fieldCount; ++i) { + + result = 31 * result + getField(i).computeHashCode(object); + + } + + return result; + } + + @Override + public boolean areEqual(StatefulObject a, StatefulObject b) { + int fieldCount = getFieldCount(); + for (int i = 0; i < fieldCount; ++i) { + if (!getField(i).areEqual(a, b)) return false; + } + + return true; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/state/HashMapStateStorage.java b/src/main/java/ru/windcorp/progressia/common/state/HashMapStateStorage.java new file mode 100644 index 0000000..2c37b23 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/state/HashMapStateStorage.java @@ -0,0 +1,20 @@ +package ru.windcorp.progressia.common.state; + +import gnu.trove.map.TIntIntMap; +import gnu.trove.map.hash.TIntIntHashMap; + +public class HashMapStateStorage extends StateStorage { + + private final TIntIntMap ints = new TIntIntHashMap(); + + @Override + public int getInt(int index) { + return ints.get(index); + } + + @Override + public void setInt(int index, int value) { + ints.put(index, value); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/state/IOContext.java b/src/main/java/ru/windcorp/progressia/common/state/IOContext.java new file mode 100644 index 0000000..c88759b --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/state/IOContext.java @@ -0,0 +1,9 @@ +package ru.windcorp.progressia.common.state; + +public enum IOContext { + + COMMS, + SAVE, + INTERNAL; + +} diff --git a/src/main/java/ru/windcorp/progressia/common/state/InspectingStatefulObjectLayout.java b/src/main/java/ru/windcorp/progressia/common/state/InspectingStatefulObjectLayout.java new file mode 100644 index 0000000..14fa24c --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/state/InspectingStatefulObjectLayout.java @@ -0,0 +1,103 @@ +package ru.windcorp.progressia.common.state; + +import java.util.ArrayList; +import java.util.List; + +public class InspectingStatefulObjectLayout +extends AbstractStatefulObjectLayout { + + private final List fields = new ArrayList<>(); + + private final PrimitiveCounters fieldIndexCounters = + new PrimitiveCounters(); + + public InspectingStatefulObjectLayout(String objectId) { + super(objectId); + } + + @Override + public StateStorage createStorage() { + return new HashMapStateStorage(); + } + + @Override + protected int getFieldCount() { + return fields.size(); + } + + @Override + protected StateField getField(int fieldIndex) { + return fields.get(fieldIndex); + } + + public StatefulObjectLayout compile() { + return new OptimizedStatefulObjectLayout( + getObjectId(), + fields, fieldIndexCounters + ); + } + + private T registerField(T field) { + fields.add(field); + return field; + } + + @Override + public StateFieldBuilder getBuilder(String namespace, String name) { + return new InspectingStateFieldBuilder( + namespace, name + ); + } + + private class InspectingStateFieldBuilder implements StateFieldBuilder { + + private class Int implements StateFieldBuilder.Int { + + @Override + public IntStateField build() { + return registerField(new IntStateField( + namespace, name, + isLocal, + fieldIndexCounters.getIntsThenIncrement() + )); + } + + } + + private final String namespace; + private final String name; + + private boolean isLocal = true; + + public InspectingStateFieldBuilder( + String namespace, String name + ) { + this.namespace = namespace; + this.name = name; + } + + @Override + public Int ofInt() { + return new Int(); + } + + @Override + public StateFieldBuilder setLocal(boolean isLocal) { + this.isLocal = isLocal; + return this; + } + + @Override + public void setOrdinal(int ordinal) { + if (ordinal != fields.size()) { + throw new IllegalStateException( + "This field is going to receive ordinal " + + fields.size() + ", requested ordinal " + + ordinal + ); + } + } + + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/state/IntStateField.java b/src/main/java/ru/windcorp/progressia/common/state/IntStateField.java new file mode 100644 index 0000000..433ccd5 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/state/IntStateField.java @@ -0,0 +1,62 @@ +package ru.windcorp.progressia.common.state; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class IntStateField extends StateField { + + public IntStateField( + String namespace, String name, + boolean isLocal, + int index + ) { + super(namespace, name, isLocal, index); + } + + public int get(StatefulObject object) { + return object.getStorage().getInt(getIndex()); + } + + public void setNow(StatefulObject object, int value) { + object.getStorage().setInt(getIndex(), value); + } + + public void set(StateChanger changer, int value) { + changer.setInt(this, value); + } + + @Override + public void read( + StatefulObject object, + DataInput input, + IOContext context + ) throws IOException { + object.getStorage().setInt(getIndex(), input.readInt()); + } + + @Override + public void write( + StatefulObject object, + DataOutput output, + IOContext context + ) throws IOException { + output.writeInt(object.getStorage().getInt(getIndex())); + } + + @Override + public void copy(StatefulObject from, StatefulObject to) { + setNow(to, get(from)); + } + + @Override + public int computeHashCode(StatefulObject object) { + return get(object); + } + + @Override + public boolean areEqual(StatefulObject a, StatefulObject b) { + return get(a) == get(b); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/state/OptimizedStateStorage.java b/src/main/java/ru/windcorp/progressia/common/state/OptimizedStateStorage.java new file mode 100644 index 0000000..ed6213b --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/state/OptimizedStateStorage.java @@ -0,0 +1,19 @@ +package ru.windcorp.progressia.common.state; + +public class OptimizedStateStorage extends StateStorage { + + private final int[] ints; + + public OptimizedStateStorage(PrimitiveCounters sizes) { + this.ints = new int[sizes.getInts()]; + } + + public int getInt(int index) { + return ints[index]; + } + + public void setInt(int index, int value) { + ints[index] = value; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/state/OptimizedStatefulObjectLayout.java b/src/main/java/ru/windcorp/progressia/common/state/OptimizedStatefulObjectLayout.java new file mode 100644 index 0000000..95e7593 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/state/OptimizedStatefulObjectLayout.java @@ -0,0 +1,68 @@ +package ru.windcorp.progressia.common.state; + +import java.util.List; + +import com.google.common.collect.ImmutableList; + +public class OptimizedStatefulObjectLayout +extends AbstractStatefulObjectLayout { + + private final List fields; + private final PrimitiveCounters sizes; + + public OptimizedStatefulObjectLayout( + String objectId, + List fields, PrimitiveCounters counters + ) { + super(objectId); + this.fields = ImmutableList.copyOf(fields); + this.sizes = new PrimitiveCounters(counters); + } + + @Override + protected int getFieldCount() { + return fields.size(); + } + + @Override + protected StateField getField(int fieldIndex) { + return fields.get(fieldIndex); + } + + @Override + public StateStorage createStorage() { + return new OptimizedStateStorage(sizes); + } + + @Override + public StateFieldBuilder getBuilder(String namespace, String name) { + return new RetrieverStateFieldBuilder(); + } + + private class RetrieverStateFieldBuilder implements StateFieldBuilder { + + private StateField result; + + @Override + public Int ofInt() { + return new Int() { + @Override + public IntStateField build() { + return (IntStateField) result; + } + }; + } + + @Override + public StateFieldBuilder setLocal(boolean isLocal) { + return this; + } + + @Override + public void setOrdinal(int ordinal) { + this.result = fields.get(ordinal); + } + + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/state/PrimitiveCounters.java b/src/main/java/ru/windcorp/progressia/common/state/PrimitiveCounters.java new file mode 100644 index 0000000..6331338 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/state/PrimitiveCounters.java @@ -0,0 +1,21 @@ +package ru.windcorp.progressia.common.state; + +class PrimitiveCounters { + + private int ints = 0; + + public PrimitiveCounters() {} + + public PrimitiveCounters(PrimitiveCounters copyFrom) { + this.ints = copyFrom.ints; + } + + public int getInts() { + return ints; + } + + public int getIntsThenIncrement() { + return this.ints++; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/state/StateChanger.java b/src/main/java/ru/windcorp/progressia/common/state/StateChanger.java new file mode 100644 index 0000000..9ef7b83 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/state/StateChanger.java @@ -0,0 +1,7 @@ +package ru.windcorp.progressia.common.state; + +public interface StateChanger { + + void setInt(IntStateField field, int value); + +} diff --git a/src/main/java/ru/windcorp/progressia/common/state/StateField.java b/src/main/java/ru/windcorp/progressia/common/state/StateField.java new file mode 100644 index 0000000..4238a8b --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/state/StateField.java @@ -0,0 +1,50 @@ +package ru.windcorp.progressia.common.state; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import ru.windcorp.progressia.common.util.Namespaced; + +public abstract class StateField extends Namespaced { + + private final boolean isLocal; + private final int index; + + public StateField( + String namespace, String name, + boolean isLocal, + int index + ) { + super(namespace, name); + this.isLocal = isLocal; + this.index = index; + } + + public boolean isLocal() { + return isLocal; + } + + protected int getIndex() { + return index; + } + + public abstract void read( + StatefulObject object, + DataInput input, + IOContext context + ) throws IOException; + + public abstract void write( + StatefulObject object, + DataOutput output, + IOContext context + ) throws IOException; + + public abstract void copy(StatefulObject from, StatefulObject to); + + public abstract int computeHashCode(StatefulObject object); + + public abstract boolean areEqual(StatefulObject a, StatefulObject b); + +} diff --git a/src/main/java/ru/windcorp/progressia/common/state/StateFieldBuilder.java b/src/main/java/ru/windcorp/progressia/common/state/StateFieldBuilder.java new file mode 100644 index 0000000..1c8a5aa --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/state/StateFieldBuilder.java @@ -0,0 +1,24 @@ +package ru.windcorp.progressia.common.state; + +public interface StateFieldBuilder { + + public static interface Int { + IntStateField build(); + } + + Int ofInt(); + + StateFieldBuilder setLocal(boolean isLocal); + + default StateFieldBuilder setLocal() { + return setLocal(true); + } + + default StateFieldBuilder setShared() { + return setLocal(false); + } + + void setOrdinal(int ordinal); + + +} diff --git a/src/main/java/ru/windcorp/progressia/common/state/StateStorage.java b/src/main/java/ru/windcorp/progressia/common/state/StateStorage.java new file mode 100644 index 0000000..5a134c9 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/state/StateStorage.java @@ -0,0 +1,9 @@ +package ru.windcorp.progressia.common.state; + +public abstract class StateStorage { + + public abstract int getInt(int index); + + public abstract void setInt(int index, int value); + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/common/state/StatefulObject.java b/src/main/java/ru/windcorp/progressia/common/state/StatefulObject.java new file mode 100644 index 0000000..f35bf19 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/state/StatefulObject.java @@ -0,0 +1,224 @@ +package ru.windcorp.progressia.common.state; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Objects; + +import ru.windcorp.progressia.common.util.Namespaced; + +/** + * An abstract class describing objects that have trackable state, + * such as blocks, tiles or entities. This class contains the declaration of + * the state mechanics + * (implementation of which is mostly delegated to + * {@link StatefulObjectLayout}). + * + *

Structure

+ * + * Stateful objects are characterized by their likeness and state. + *

+ * An object's likeness is the combination of the object's runtime class (as in + * {@link #getClass()}) and its ID. Objects that are "alike" share the same + * internal structure, represented by a common + * {@linkplain StatefulObjectLayout layout}. Likeness can be tested with + * {@link #isLike(Object)}. + *

+ * An object's state is the combination of the values of an object's + * {@linkplain StateField state fields}. State fields are different from object + * fields as described by the Java language: not every object field is a part + * of its state, although state fields are usually implemented as object fields. + * Each state field is, in its turn, has the following characteristics: + *

    + *
  • ID, distinct from the ID of the stateful object to which it belongs. + * State field IDs are only unique within fields of one likeness.
  • + *
  • data type, which is one of Java primitive types or a compound (Object) + * type.
  • + *
+ */ +public abstract class StatefulObject extends Namespaced { + + private final StatefulObjectLayout layout; + + private final StateStorage storage; + + public StatefulObject( + StatefulObjectRegistry type, + String namespace, + String name + ) { + super(namespace, name); + this.layout = type.getLayout(getId()); + this.storage = getLayout().createStorage(); + } + + /** + * Returns the {@link StatefulObjectLayout} describing objects that are + * {@linkplain #isLike(Object) "like"} this object. + * You probably don't need this. + * + * @return this object's field layout + */ + public StatefulObjectLayout getLayout() { + return layout; + } + + /** + * Returns a {@link StateStorage} used by this object to store its state in + * memory. You probably don't need this. + * + * @return this object's state storage + */ + public StateStorage getStorage() { + return storage; + } + + /* + * Field construction + */ + + /** + * Used to keep track of the ordinal number of the next field to be + * requested. + */ + private int fieldOrdinal = 0; + + /** + * Returns a {@link StateFieldBuilder} set up to construct a field with the + * specified ID. + *

+ * This method must only be called from the constructor, and the same + * sequence of invocations must occur during construction of each object + * with the same ID. + * + * @param namespace the namespace of the new field + * @param name the name of the new field + * + * @return a configured builder + */ + protected StateFieldBuilder field(String namespace, String name) { + StateFieldBuilder builder = getLayout().getBuilder(namespace, name); + + builder.setOrdinal(fieldOrdinal); + fieldOrdinal++; + + return builder; + } + + /* + * IO + */ + + /** + * Sets the state of this object according to the binary representation read + * from {@code input}. If {@code context == COMMS}, the state of local + * fields is unspecified after this operation. + * + * @param input a {@link DataInput} that a state can be read from + * @param context the context + * + * @throws IOException if the state is encoded poorly or an error occurs + * in {@code input} + */ + public void read(DataInput input, IOContext context) throws IOException { + getLayout().read(this, input, context); + } + + /** + * Writes the binary representation of the state of this object to the + * {@code output}. + * + * @param output a {@link DataOutput} that a state can be written to + * @param context the context + * + * @throws IOException if an error occurs in {@code output} + */ + public void write(DataOutput output, IOContext context) throws IOException { + getLayout().write(this, output, context); + } + + /* + * Identity operations + */ + + /** + * Turns {@code destination} into a deep copy of this object. + *

+ * Changes the provided object so that: + *

    + *
  • the provided object equals this object according to + * {@link StatefulObject#equals(Object)}; and
  • + *
  • the provided object is independent of this object, meaning no change + * to {@code destination} can affect this object.
  • + *
+ * + * @param destination the object to copy this object into. + */ + public StatefulObject copy(StatefulObject destination) { + Objects.requireNonNull(destination, "destination"); + + if (destination == this) { + throw new IllegalArgumentException( + "Cannot copy an object into itself" + ); + } + + if (destination.getClass() != this.getClass()) { + throw new IllegalArgumentException( + "Cannot copy from " + getClass() + + " (ID " + getId() + ") to " + destination.getClass() + ); + } + + getLayout().copy(this, destination); + return destination; + } + + /** + * @see #equals(Object) + */ + @Override + public int hashCode() { + return getLayout().computeHashCode(this); + } + + /** + * Determines whether this object and {@code obj} have equal states. + * Stateful objects are considered equal iff they are + * {@linkplain #isLike(Object) "like"} and their binary representations + * match exactly. + * + * @param obj the object to examine + * @return {@code true} if {@code obj != null} and this object is equal to + * {@code obj} + */ + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (!this.isLike(obj)) return false; + + return getLayout().areEqual(this, (StatefulObject) obj); + } + + /** + * Checks whether the provided object is "like" this object. + *

+ * Returns {@code true} iff this object and {@code obj} have the same ID and + * are instances of the same class. + * + * @param obj the object to examine + * @return {@code true} if {@code obj} is "like" this object + */ + public boolean isLike(Object obj) { + if (obj == null) return false; + if (obj == this) return true; + if (obj.getClass() != this.getClass()) return false; + + StatefulObject statefulObj = (StatefulObject) obj; + + if (statefulObj.getId().equals(this.getId())) return false; + + return true; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/state/StatefulObjectLayout.java b/src/main/java/ru/windcorp/progressia/common/state/StatefulObjectLayout.java new file mode 100644 index 0000000..5c36487 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/state/StatefulObjectLayout.java @@ -0,0 +1,48 @@ +package ru.windcorp.progressia.common.state; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public abstract class StatefulObjectLayout { + + private final String objectId; + + public StatefulObjectLayout(String objectId) { + this.objectId = objectId; + } + + public String getObjectId() { + return objectId; + } + + public abstract StateStorage createStorage(); + + protected void checkObject(StatefulObject object) { + if (!object.getId().equals(getObjectId())) { + throw new IllegalArgumentException( + object.getId() + " is not " + getObjectId() + ); + } + } + + public abstract void read( + StatefulObject object, + DataInput input, + IOContext context + ) throws IOException; + + public abstract void write( + StatefulObject object, + DataOutput output, + IOContext context + ) throws IOException; + + public abstract void copy(StatefulObject from, StatefulObject to); + + public abstract int computeHashCode(StatefulObject object); + public abstract boolean areEqual(StatefulObject a, StatefulObject b); + + public abstract StateFieldBuilder getBuilder(String namespace, String name); + +} diff --git a/src/main/java/ru/windcorp/progressia/common/state/StatefulObjectRegistry.java b/src/main/java/ru/windcorp/progressia/common/state/StatefulObjectRegistry.java new file mode 100644 index 0000000..1c118ab --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/state/StatefulObjectRegistry.java @@ -0,0 +1,98 @@ +package ru.windcorp.progressia.common.state; + +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.util.NamespacedRegistry; + +/** + * Registry-like object for identification of various {@link StatefulObject} + * types, such as blocks vs tiles. This object stores and manages various + * {@linkplain StatefulObjectLayout layouts}. + */ +public class StatefulObjectRegistry { + + @FunctionalInterface + public static interface Factory { + /** + * Initializes a new, independent instance of the stateful object. + * @return the created object + */ + T build(); + } + + protected static class Type extends Namespaced { + + private final Factory factory; + + private final AtomicBoolean isRegistered = new AtomicBoolean(false); + + public Type(String namespace, String name, Factory factory) { + super(namespace, name); + this.factory = factory; + } + + public T build() { + return factory.build(); + } + + public AtomicBoolean getRegistrationFlag() { + return isRegistered; + } + + } + + private final NamespacedRegistry> registry = + new NamespacedRegistry>() { + @Override + public void register(Type element) { + super.register(element); + StatefulObjectRegistry.this.register(element); + }; + }; + + private final Map layouts = + Collections.synchronizedMap(new WeakHashMap<>()); + + public StatefulObjectLayout getLayout(String id) { + StatefulObjectLayout layout = layouts.get(id); + + if (layout == null) { + throw new IllegalArgumentException( + "ID " + id + " has not been registered" + ); + } + + return layout; + } + + protected void register(Type type) { + if (!type.getRegistrationFlag().compareAndSet(false, true)) { + throw new IllegalStateException( + "ID " + type.getId() + " is already registered" + ); + } + + InspectingStatefulObjectLayout inspector = + new InspectingStatefulObjectLayout(type.getId()); + + layouts.put(type.getId(), inspector); + + // During initialization inspector collects necessary data + type.build(); + + layouts.put(type.getId(), inspector.compile()); + } + + public T create(String id) { + return registry.get(id).build(); + } + + public void register(String namespace, String name, Factory factory) { + registry.register(new Type<>(namespace, name, factory)); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/util/CoordinatePacker.java b/src/main/java/ru/windcorp/progressia/common/util/CoordinatePacker.java index a8b1c29..0331df2 100644 --- a/src/main/java/ru/windcorp/progressia/common/util/CoordinatePacker.java +++ b/src/main/java/ru/windcorp/progressia/common/util/CoordinatePacker.java @@ -17,6 +17,9 @@ *******************************************************************************/ package ru.windcorp.progressia.common.util; +import glm.vec._2.i.Vec2i; +import glm.vec._3.i.Vec3i; + public class CoordinatePacker { private static final int BITS_3_INTS_INTO_LONG; @@ -53,6 +56,10 @@ public class CoordinatePacker { ((c & MASK_3_INTS_INTO_LONG) << (0 * BITS_3_INTS_INTO_LONG)); } + public static long pack3IntsIntoLong(Vec3i v) { + return pack3IntsIntoLong(v.x, v.y, v.z); + } + public static int unpack3IntsFromLong(long packed, int index) { if (index < 0 || index >= 3) { throw new IllegalArgumentException("Invalid index " + index); @@ -72,12 +79,26 @@ public class CoordinatePacker { return result; } + public static Vec3i unpack3IntsFromLong(long packed, Vec3i output) { + output.set( + unpack3IntsFromLong(packed, 0), + unpack3IntsFromLong(packed, 1), + unpack3IntsFromLong(packed, 2) + ); + + return output; + } + public static long pack2IntsIntoLong(int a, int b) { return ((a & MASK_2_INTS_INTO_LONG) << (1 * BITS_2_INTS_INTO_LONG)) | ((b & MASK_2_INTS_INTO_LONG) << (0 * BITS_2_INTS_INTO_LONG)); } + public static long pack2IntsIntoLong(Vec2i v) { + return pack2IntsIntoLong(v.x, v.y); + } + public static int unpack2IntsFromLong(long packed, int index) { if (index < 0 || index >= 2) { throw new IllegalArgumentException("Invalid index " + index); @@ -90,5 +111,14 @@ public class CoordinatePacker { return result; } + + public static Vec2i unpack2IntsFromLong(long packed, Vec2i output) { + output.set( + unpack2IntsFromLong(packed, 0), + unpack2IntsFromLong(packed, 1) + ); + + return output; + } } diff --git a/src/main/java/ru/windcorp/progressia/common/util/DataBuffer.java b/src/main/java/ru/windcorp/progressia/common/util/DataBuffer.java new file mode 100644 index 0000000..00f7fd3 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/util/DataBuffer.java @@ -0,0 +1,121 @@ +package ru.windcorp.progressia.common.util; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import gnu.trove.list.array.TByteArrayList; + +public class DataBuffer { + + private static final int DEFAULT_CAPACITY = 1024; + private static final int TRANSFER_BUFFER_SIZE = 1024; + + private final TByteArrayList buffer; + + private final byte[] transferBuffer = new byte[TRANSFER_BUFFER_SIZE]; + + private int position; + + private final DataInput reader = new DataInputStream( + new InputStream() { + @Override + public int read() throws IOException { + if (position >= buffer.size()) return -1; + int result = buffer.getQuick(position); + ++position; + return result; + } + } + ); + + private final DataOutput writer = new DataOutputStream( + new OutputStream() { + @Override + public void write(int b) throws IOException { + buffer.add((byte) b); + } + } + ); + + public DataBuffer(int capacity) { + this.buffer = new TByteArrayList(capacity); + } + + public DataBuffer() { + this(DEFAULT_CAPACITY); + } + + public DataBuffer(DataBuffer copyFrom) { + this.buffer = new TByteArrayList(copyFrom.buffer); + } + + public DataInput getReader() { + position = 0; + return reader; + } + + public DataOutput getWriter() { + buffer.resetQuick(); + return writer; + } + + public int getSize() { + return buffer.size(); + } + + public void fill(DataInput source, int length) throws IOException { + buffer.resetQuick(); + buffer.ensureCapacity(length); + + while (length > 0) { + int currentLength = Math.min(transferBuffer.length, length); + + source.readFully(transferBuffer, 0, currentLength); + buffer.add(transferBuffer, 0, currentLength); + + length -= currentLength; + } + } + + public void flush(DataOutput sink) throws IOException { + int position = 0; + int length = buffer.size(); + + while (position < length) { + int currentLength = Math.min( + transferBuffer.length, + length - position + ); + + buffer.toArray(transferBuffer, position, 0, currentLength); + sink.write(transferBuffer, 0, currentLength); + + length -= currentLength; + } + } + + @Override + public int hashCode() { + return buffer.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + DataBuffer other = (DataBuffer) obj; + if (!buffer.equals(other.buffer)) + return false; + return true; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/util/NamespacedRegistry.java b/src/main/java/ru/windcorp/progressia/common/util/NamespacedRegistry.java index 5aa457c..a2ce835 100644 --- a/src/main/java/ru/windcorp/progressia/common/util/NamespacedRegistry.java +++ b/src/main/java/ru/windcorp/progressia/common/util/NamespacedRegistry.java @@ -6,6 +6,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import org.apache.logging.log4j.LogManager; + import com.google.errorprone.annotations.DoNotCall; public class NamespacedRegistry @@ -15,6 +17,7 @@ implements Map { Collections.synchronizedMap(new HashMap<>()); public void register(E element) { + LogManager.getLogger(getClass()).debug("Registering " + element.getId()); backingMap.put(element.getId(), element); } diff --git a/src/main/java/ru/windcorp/progressia/common/util/NamespacedUtil.java b/src/main/java/ru/windcorp/progressia/common/util/NamespacedUtil.java new file mode 100644 index 0000000..2e5dafd --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/util/NamespacedUtil.java @@ -0,0 +1,46 @@ +package ru.windcorp.progressia.common.util; + +import java.util.Objects; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +public class NamespacedUtil { + + public static final char SEPARATOR = ':'; + public static final int MAX_PART_LENGTH = 127; + public static final int MAX_TOTAL_LENGTH = MAX_PART_LENGTH * 2 + 1; + + private static final String PART_REGEX = "^[A-Z][a-zA-Z0-9]{2,}$"; + + private static final Predicate PART_CHECKER = + Pattern.compile(PART_REGEX).asPredicate(); + + public static String getId(String namespace, String name) { + checkPart(namespace, "Namespace"); + checkPart(name, "Name"); + + return namespace + SEPARATOR + name; + } + + private static void checkPart(String data, String name) { + Objects.requireNonNull(data, name); + + if (data.length() > MAX_PART_LENGTH) { + throw new IllegalArgumentException( + name + " \"" + data + "\" is too long. " + + "Expected at most " + MAX_PART_LENGTH + + " characters" + ); + } + + if (!PART_CHECKER.test(name)) { + throw new IllegalArgumentException( + name + " \"" + data + "\" is invalid. " + + "Allowed is: " + PART_REGEX + ); + } + } + + private NamespacedUtil() {} + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java index 681b779..18d44b0 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java @@ -22,7 +22,6 @@ import static ru.windcorp.progressia.common.world.block.BlockFace.*; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -39,6 +38,7 @@ import ru.windcorp.progressia.common.world.block.BlockData; import ru.windcorp.progressia.common.world.block.BlockDataRegistry; import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.common.world.entity.EntityDataRegistry; import ru.windcorp.progressia.common.world.tile.TileData; import ru.windcorp.progressia.common.world.tile.TileDataRegistry; @@ -132,13 +132,18 @@ public class ChunkData { } } - EntityData javapony = new EntityData("Test", "Javapony"); - javapony.setUUID(UUID.nameUUIDFromBytes(new byte[] {42})); + EntityData javapony = EntityDataRegistry.getInstance().create("Test:Javapony"); + javapony.setEntityId(0x42); javapony.setPosition(new Vec3(-6, -6, 20)); javapony.setDirection(new Vec2( (float) Math.toRadians(40), (float) Math.toRadians(45) )); getEntities().add(javapony); + + EntityData statie = EntityDataRegistry.getInstance().create("Test:Statie"); + statie.setEntityId(0xDEADBEEF); + statie.setPosition(new Vec3(0, 15, 16)); + getEntities().add(statie); } public BlockData getBlock(Vec3i posInChunk) { diff --git a/src/main/java/ru/windcorp/progressia/common/world/WorldData.java b/src/main/java/ru/windcorp/progressia/common/world/WorldData.java index 6e35b8b..6440709 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/WorldData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/WorldData.java @@ -21,28 +21,59 @@ import java.util.Collection; import java.util.Collections; import glm.vec._3.i.Vec3i; +import gnu.trove.impl.sync.TSynchronizedLongObjectMap; import gnu.trove.map.TLongObjectMap; import gnu.trove.map.hash.TLongObjectHashMap; import ru.windcorp.progressia.common.util.CoordinatePacker; import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.world.entity.EntityData; public class WorldData { - private final TLongObjectMap chunks = new TLongObjectHashMap<>(); + private final TLongObjectMap chunksByPos = + new TSynchronizedLongObjectMap<>(new TLongObjectHashMap<>(), this); + + private final Collection chunks = + Collections.unmodifiableCollection(chunksByPos.valueCollection()); + + private final TLongObjectMap entitiesById = + new TSynchronizedLongObjectMap<>(new TLongObjectHashMap<>(), this); + + private final Collection entities = + Collections.unmodifiableCollection(entitiesById.valueCollection()); public WorldData() { final int size = 1; for (int x = -(size / 2); x <= (size / 2); ++x) { for (int y = -(size / 2); y <= (size / 2); ++y) { - chunks.put(CoordinatePacker.pack3IntsIntoLong(x, y, 0), new ChunkData(x, y, 0, this)); + addChunk(new ChunkData(x, y, 0, this)); } } } + private synchronized void addChunk(ChunkData chunk) { + chunksByPos.put(getChunkKey(chunk), chunk); + + chunk.forEachEntity(entity -> + entitiesById.put(entity.getEntityId(), entity) + ); + } + +// private synchronized void removeChunk(ChunkData chunk) { +// chunksByPos.remove(getChunkKey(chunk)); +// +// chunk.forEachEntity(entity -> +// entitiesById.remove(entity.getEntityId()) +// ); +// } + + private static long getChunkKey(ChunkData chunk) { + return CoordinatePacker.pack3IntsIntoLong(chunk.getPosition()); + } + public ChunkData getChunk(Vec3i pos) { - long key = CoordinatePacker.pack3IntsIntoLong(pos.x, pos.y, pos.z); - return chunks.get(key); + return chunksByPos.get(CoordinatePacker.pack3IntsIntoLong(pos)); } public ChunkData getChunkByBlock(Vec3i blockInWorld) { @@ -54,7 +85,15 @@ public class WorldData { } public Collection getChunks() { - return Collections.unmodifiableCollection(chunks.valueCollection()); + return chunks; + } + + public EntityData getEntity(long entityId) { + return entitiesById.get(entityId); + } + + public Collection getEntities() { + return entities; } } diff --git a/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java b/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java index 1748104..1c8e7e8 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java @@ -1,22 +1,22 @@ package ru.windcorp.progressia.common.world.entity; -import java.util.UUID; - import glm.vec._2.Vec2; import glm.vec._3.Vec3; -import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.state.StatefulObject; -public class EntityData extends Namespaced { +public class EntityData extends StatefulObject { private final Vec3 position = new Vec3(); private final Vec3 velocity = new Vec3(); private final Vec2 direction = new Vec2(); - private UUID uuid; + private long entityId; + + private double age = 0; public EntityData(String namespace, String name) { - super(namespace, name); + super(EntityDataRegistry.getInstance(), namespace, name); } public Vec3 getPosition() { @@ -51,12 +51,24 @@ public class EntityData extends Namespaced { return getDirection().y; } - public UUID getUUID() { - return uuid; + public long getEntityId() { + return entityId; } - public void setUUID(UUID uuid) { - this.uuid = uuid; + public void setEntityId(long entityId) { + this.entityId = entityId; + } + + public double getAge() { + return age; + } + + public void setAge(double age) { + this.age = age; + } + + public void incrementAge(double increment) { + this.age += increment; } } diff --git a/src/main/java/ru/windcorp/progressia/common/world/entity/EntityDataRegistry.java b/src/main/java/ru/windcorp/progressia/common/world/entity/EntityDataRegistry.java index 8457541..5624b71 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/entity/EntityDataRegistry.java +++ b/src/main/java/ru/windcorp/progressia/common/world/entity/EntityDataRegistry.java @@ -1,13 +1,17 @@ package ru.windcorp.progressia.common.world.entity; -import ru.windcorp.progressia.common.util.NamespacedRegistry; +import ru.windcorp.progressia.common.state.StatefulObjectRegistry; -public class EntityDataRegistry extends NamespacedRegistry { +public class EntityDataRegistry extends StatefulObjectRegistry { private static final EntityDataRegistry INSTANCE = new EntityDataRegistry(); public static EntityDataRegistry getInstance() { return INSTANCE; } + + public void register(String namespace, String name) { + super.register(namespace, name, () -> new EntityData(namespace, name)); + } } diff --git a/src/main/java/ru/windcorp/progressia/common/world/entity/PacketEntityChange.java b/src/main/java/ru/windcorp/progressia/common/world/entity/PacketEntityChange.java new file mode 100644 index 0000000..f6c5c8b --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/entity/PacketEntityChange.java @@ -0,0 +1,60 @@ +package ru.windcorp.progressia.common.world.entity; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import ru.windcorp.progressia.common.comms.packets.PacketWorldChange; +import ru.windcorp.progressia.common.state.IOContext; +import ru.windcorp.progressia.common.util.DataBuffer; +import ru.windcorp.progressia.common.world.WorldData; + +public class PacketEntityChange extends PacketWorldChange { + + private long entityId; + private final DataBuffer buffer = new DataBuffer(); + + public PacketEntityChange() { + super("Core", "EntityChange"); + } + + public long getEntityId() { + return entityId; + } + + public void setEntityId(long entityId) { + this.entityId = entityId; + } + + public DataBuffer getBuffer() { + return buffer; + } + + public DataInput getReader() { + return buffer.getReader(); + } + + public DataOutput getWriter() { + return buffer.getWriter(); + } + + @Override + public void apply(WorldData world) { + EntityData entity = world.getEntity(getEntityId()); + + if (entity == null) { + throw new RuntimeException( + "Entity with ID " + getEntityId() + " not found" + ); + } + + try { + entity.read(getReader(), IOContext.COMMS); + } catch (IOException e) { + throw new RuntimeException( + "Entity could not be read", e + ); + } + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/ServerThread.java b/src/main/java/ru/windcorp/progressia/server/ServerThread.java index b6f7b17..ab980c0 100644 --- a/src/main/java/ru/windcorp/progressia/server/ServerThread.java +++ b/src/main/java/ru/windcorp/progressia/server/ServerThread.java @@ -4,6 +4,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.LogManager; + import ru.windcorp.progressia.server.world.Ticker; public class ServerThread implements Runnable { @@ -50,8 +52,12 @@ public class ServerThread implements Runnable { @Override public void run() { - server.tick(); - ticker.run(); + try { + server.tick(); + ticker.run(); + } catch (Exception e) { + LogManager.getLogger(getClass()).error("Got an exception in server thread", e); + } } public Server getServer() { diff --git a/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java b/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java index 6f9381b..86d5cff 100644 --- a/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java +++ b/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java @@ -4,7 +4,6 @@ import java.util.Collection; import java.util.Collections; import java.util.concurrent.atomic.AtomicInteger; -import glm.vec._3.i.Vec3i; import gnu.trove.TCollections; import gnu.trove.map.TIntObjectMap; import gnu.trove.map.hash.TIntObjectHashMap; @@ -39,10 +38,7 @@ public class ClientManager { client.addListener(new DefaultServerCommsListener(this, client)); - client.sendPacket(new PacketSetLocalPlayer( - server.getWorld().getData().getChunk(new Vec3i(0, 0, 0)) - .getEntities().get(0).getUUID() - )); + client.sendPacket(new PacketSetLocalPlayer(0x42)); } public void disconnectClient(Client client) { diff --git a/src/main/java/ru/windcorp/progressia/server/world/Changer.java b/src/main/java/ru/windcorp/progressia/server/world/Changer.java index 3901bca..e4aee3d 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/Changer.java +++ b/src/main/java/ru/windcorp/progressia/server/world/Changer.java @@ -3,14 +3,22 @@ package ru.windcorp.progressia.server.world; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.block.BlockData; import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.tile.TileData; public interface Changer { + + @FunctionalInterface + public static interface Change { + void change(T object); + } void setBlock(Vec3i pos, BlockData block); void addTile(Vec3i block, BlockFace face, TileData tile); void removeTile(Vec3i block, BlockFace face, TileData tile); + + void changeEntity(T entity, Change change); } \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/server/world/ImplementedChangeTracker.java b/src/main/java/ru/windcorp/progressia/server/world/ImplementedChangeTracker.java index 675befe..70fada1 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/ImplementedChangeTracker.java +++ b/src/main/java/ru/windcorp/progressia/server/world/ImplementedChangeTracker.java @@ -1,5 +1,6 @@ package ru.windcorp.progressia.server.world; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -7,25 +8,28 @@ import java.util.Objects; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.comms.packets.Packet; import ru.windcorp.progressia.common.comms.packets.PacketWorldChange; +import ru.windcorp.progressia.common.state.IOContext; import ru.windcorp.progressia.common.util.LowOverheadCache; import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.world.Coordinates; import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.block.BlockData; import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.common.world.entity.PacketEntityChange; import ru.windcorp.progressia.common.world.tile.TileData; import ru.windcorp.progressia.server.Server; public class ImplementedChangeTracker implements Changer { - public static interface Change { + public static interface ChangeImplementation { void applyOnServer(WorldData world); Packet asPacket(); } private static class SetBlock extends PacketWorldChange - implements Change { + implements ChangeImplementation { private final Vec3i position = new Vec3i(); private BlockData block; @@ -63,7 +67,7 @@ public class ImplementedChangeTracker implements Changer { private static class AddOrRemoveTile extends PacketWorldChange - implements Change { + implements ChangeImplementation { private final Vec3i position = new Vec3i(); private BlockFace face; @@ -114,8 +118,46 @@ public class ImplementedChangeTracker implements Changer { } } + + private static class ChangeEntity implements ChangeImplementation { + + private EntityData entity; + private Change change; + + private final PacketEntityChange packet = new PacketEntityChange(); - private final List changes = new ArrayList<>(1024); + public void set(T entity, Change change) { + this.entity = entity; + this.change = change; + + packet.setEntityId(entity.getEntityId()); + try { + entity.write(packet.getWriter(), IOContext.COMMS); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + @Override + public void applyOnServer(WorldData world) { + ((Change) change).change(entity); + + try { + entity.write(packet.getWriter(), IOContext.COMMS); + } catch (IOException e) { + throw new RuntimeException("Entity could not be written", e); + } + } + + @Override + public Packet asPacket() { + return packet; + } + + } + + private final List changes = new ArrayList<>(1024); private final LowOverheadCache setBlockCache = new LowOverheadCache<>(SetBlock::new); @@ -123,6 +165,9 @@ public class ImplementedChangeTracker implements Changer { private final LowOverheadCache addOrRemoveTileCache = new LowOverheadCache<>(AddOrRemoveTile::new); + private final LowOverheadCache changeEntityCache = + new LowOverheadCache<>(ChangeEntity::new); + @Override public void setBlock(Vec3i pos, BlockData block) { SetBlock change = setBlockCache.grab(); @@ -144,12 +189,34 @@ public class ImplementedChangeTracker implements Changer { changes.add(change); } + @Override + public void changeEntity( + T entity, Change change + ) { + ChangeEntity changeRecord = changeEntityCache.grab(); + changeRecord.set(entity, change); + changes.add(changeRecord); + } + public void applyChanges(Server server) { changes.forEach(c -> c.applyOnServer(server.getWorld().getData())); - changes.stream().map(Change::asPacket).filter(Objects::nonNull).forEach( + changes.stream().map(ChangeImplementation::asPacket).filter(Objects::nonNull).forEach( server.getClientManager()::broadcastGamePacket ); + changes.forEach(this::release); changes.clear(); } + + private void release(ChangeImplementation c) { + if (c instanceof SetBlock) { + setBlockCache.release((SetBlock) c); + } else if (c instanceof AddOrRemoveTile) { + addOrRemoveTileCache.release((AddOrRemoveTile) c); + } else if (c instanceof ChangeEntity) { + changeEntityCache.release((ChangeEntity) c); + } else { + throw new IllegalArgumentException("Could not find cache for " + c); + } + } } diff --git a/src/main/java/ru/windcorp/progressia/server/world/Ticker.java b/src/main/java/ru/windcorp/progressia/server/world/Ticker.java index ec4b4e5..4aa1bdc 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/Ticker.java +++ b/src/main/java/ru/windcorp/progressia/server/world/Ticker.java @@ -2,6 +2,8 @@ package ru.windcorp.progressia.server.world; import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.world.block.TickableBlock; +import ru.windcorp.progressia.server.world.entity.EntityLogic; +import ru.windcorp.progressia.server.world.entity.EntityLogicRegistry; import ru.windcorp.progressia.server.world.tile.TickableTile; public class Ticker implements Runnable { @@ -36,12 +38,32 @@ public class Ticker implements Runnable { blockContext.setServer(server); tileContext.setServer(server); + blockContext.setChunk(chunk); + tileContext.setChunk(chunk); + tickRegularTickers(chunk, blockContext, tileContext); tickRandomBlocks(chunk, blockContext, tileContext); + tickEntities(chunk, blockContext); + flushChanges(chunk); } + private void tickEntities( + ChunkLogic chunk, + MutableChunkTickContext tickContext + ) { + // TODO this is ugly + + chunk.getData().getEntities().forEach(entity -> { + EntityLogic logic = EntityLogicRegistry.getInstance().get( + entity.getId() + ); + + logic.tick(entity, tickContext, tracker); + }); + } + private void tickRegularTickers( ChunkLogic chunk, MutableBlockTickContext blockContext, diff --git a/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogic.java b/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogic.java index c3c09e6..22f31c5 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogic.java @@ -1,11 +1,18 @@ package ru.windcorp.progressia.server.world.entity; import ru.windcorp.progressia.common.util.Namespaced; +import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.server.world.Changer; +import ru.windcorp.progressia.server.world.TickContext; public class EntityLogic extends Namespaced { public EntityLogic(String namespace, String name) { super(namespace, name); } + + public void tick(EntityData entity, TickContext context, Changer changer) { + entity.incrementAge(context.getTickLength()); + } } diff --git a/src/main/java/ru/windcorp/progressia/client/TestContent.java b/src/main/java/ru/windcorp/progressia/test/TestContent.java similarity index 86% rename from src/main/java/ru/windcorp/progressia/client/TestContent.java rename to src/main/java/ru/windcorp/progressia/test/TestContent.java index d49b081..3463525 100644 --- a/src/main/java/ru/windcorp/progressia/client/TestContent.java +++ b/src/main/java/ru/windcorp/progressia/test/TestContent.java @@ -1,4 +1,4 @@ -package ru.windcorp.progressia.client; +package ru.windcorp.progressia.test; import static ru.windcorp.progressia.client.world.block.BlockRenderRegistry.getBlockTexture; import static ru.windcorp.progressia.client.world.tile.TileRenderRegistry.getTileTexture; @@ -12,6 +12,7 @@ import ru.windcorp.progressia.client.world.block.*; import ru.windcorp.progressia.client.world.entity.*; import ru.windcorp.progressia.client.world.tile.*; import ru.windcorp.progressia.common.comms.controls.*; +import ru.windcorp.progressia.common.state.StatefulObjectRegistry.Factory; import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.block.*; import ru.windcorp.progressia.common.world.entity.*; @@ -77,9 +78,13 @@ public class TestContent { } private static void registerEntities() { - register(new EntityData("Test", "Javapony")); + registerEntityData("Test", "Javapony"); register(new TestEntityRenderJavapony()); register(new EntityLogic("Test", "Javapony")); + + register("Test", "Statie", TestEntityDataStatie::new); + register(new TestEntityRenderStatie()); + register(new TestEntityLogicStatie()); } private static void regsiterControls() { @@ -112,8 +117,17 @@ public class TestContent { TileDataRegistry.getInstance().register(x); } - private static void register(EntityData x) { - EntityDataRegistry.getInstance().register(x); + private static void register( + String namespace, String name, + Factory factory + ) { + EntityDataRegistry.getInstance().register(namespace, name, factory); + } + + private static void registerEntityData( + String namespace, String name + ) { + EntityDataRegistry.getInstance().register(namespace, name); } private static void register(BlockRender x) { diff --git a/src/main/java/ru/windcorp/progressia/test/TestEntityDataStatie.java b/src/main/java/ru/windcorp/progressia/test/TestEntityDataStatie.java new file mode 100644 index 0000000..2ff45c8 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/TestEntityDataStatie.java @@ -0,0 +1,24 @@ +package ru.windcorp.progressia.test; + +import ru.windcorp.progressia.common.state.IntStateField; +import ru.windcorp.progressia.common.world.entity.EntityData; + +public class TestEntityDataStatie extends EntityData { + + private final IntStateField size = + field("Test", "Size").setShared().ofInt().build(); + + public TestEntityDataStatie() { + super("Test", "Statie"); + setSizeNow(16); + } + + public int getSize() { + return size.get(this); + } + + public void setSizeNow(int size) { + this.size.setNow(this, size); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/TestEntityLogicStatie.java b/src/main/java/ru/windcorp/progressia/test/TestEntityLogicStatie.java new file mode 100644 index 0000000..9826938 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/TestEntityLogicStatie.java @@ -0,0 +1,24 @@ +package ru.windcorp.progressia.test; + +import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.server.world.Changer; +import ru.windcorp.progressia.server.world.TickContext; +import ru.windcorp.progressia.server.world.entity.EntityLogic; + +public class TestEntityLogicStatie extends EntityLogic { + + public TestEntityLogicStatie() { + super("Test", "Statie"); + } + + @Override + public void tick(EntityData entity, TickContext context, Changer changer) { + super.tick(entity, context, changer); + + TestEntityDataStatie statie = (TestEntityDataStatie) entity; + + int size = (int) (18 + 6 * Math.sin(entity.getAge())); + changer.changeEntity(statie, e -> e.setSizeNow(size)); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/client/TestEntityRenderJavapony.java b/src/main/java/ru/windcorp/progressia/test/TestEntityRenderJavapony.java similarity index 95% rename from src/main/java/ru/windcorp/progressia/client/TestEntityRenderJavapony.java rename to src/main/java/ru/windcorp/progressia/test/TestEntityRenderJavapony.java index b1e8a92..61c15f3 100644 --- a/src/main/java/ru/windcorp/progressia/client/TestEntityRenderJavapony.java +++ b/src/main/java/ru/windcorp/progressia/test/TestEntityRenderJavapony.java @@ -1,4 +1,4 @@ -package ru.windcorp.progressia.client; +package ru.windcorp.progressia.test; import java.util.ArrayList; import java.util.List; @@ -320,7 +320,7 @@ public class TestEntityRenderJavapony extends EntityRender { new QuadripedModel.Body(body), new QuadripedModel.Head( - head, new Vec3(12, 0, 20), 60, 45, new Vec3(16, 0, 20) + head, new Vec3(12, 0, 20), 120, 45, new Vec3(16, 0, 20) ), new QuadripedModel.Leg( leftForeLeg, new Vec3( 6, +8.1f, -16), 0.0f diff --git a/src/main/java/ru/windcorp/progressia/test/TestEntityRenderStatie.java b/src/main/java/ru/windcorp/progressia/test/TestEntityRenderStatie.java new file mode 100644 index 0000000..ce388f8 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/TestEntityRenderStatie.java @@ -0,0 +1,42 @@ +package ru.windcorp.progressia.test; + +import ru.windcorp.progressia.client.graphics.model.Renderable; +import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; +import ru.windcorp.progressia.client.graphics.model.Shapes; +import ru.windcorp.progressia.client.graphics.texture.Texture; +import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; +import ru.windcorp.progressia.client.world.entity.EntityRender; +import ru.windcorp.progressia.client.world.entity.EntityRenderable; +import ru.windcorp.progressia.common.world.entity.EntityData; + +public class TestEntityRenderStatie extends EntityRender { + + private final Renderable cube = + new Shapes.PppBuilder( + WorldRenderProgram.getDefault(), + (Texture) null + ) + .setColorMultiplier(1, 1, 0) + .create(); + + public TestEntityRenderStatie() { + super("Test", "Statie"); + } + + @Override + public EntityRenderable createRenderable(EntityData entity) { + return new EntityRenderable(entity) { + @Override + public void render(ShapeRenderHelper renderer) { + renderer.pushTransform().scale( + ((TestEntityDataStatie) entity).getSize() / 24.0f + ); + + cube.render(renderer); + + renderer.popTransform(); + } + }; + } + +} From b59c4bdc2b1433884662b6a33ea0a66427b59da5 Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Sun, 4 Oct 2020 19:50:52 +0300 Subject: [PATCH 2/2] Camera now stores last applied position and direction Also FloatMathUtils now has sin, cos and tan --- .../client/graphics/world/Camera.java | 101 +++++++++++++++++- .../common/util/FloatMathUtils.java | 12 +++ 2 files changed, 110 insertions(+), 3 deletions(-) diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java index 7e8a59d..ab99777 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java @@ -18,6 +18,7 @@ package ru.windcorp.progressia.client.graphics.world; import static java.lang.Math.*; +import static ru.windcorp.progressia.common.util.FloatMathUtils.*; import java.util.Collection; import java.util.function.Consumer; @@ -74,11 +75,32 @@ public class Camera { private float fieldOfView; + /* + * Cache + */ + + private final Vec3 lastAnchorPosition = new Vec3(); + private float lastAnchorYaw; + private float lastAnchorPitch; + + private final Mat4 lastCameraMatrix = new Mat4(); + + private final Vec3 lastAnchorLookingAt = new Vec3(); + private final Vec3 lastAnchorUp = new Vec3(); + + { + invalidateCache(); + } + public Camera(float fieldOfView) { setFieldOfView(fieldOfView); } public Camera() {} + + /* + * apply() and subroutines + */ public void apply(WorldRenderHelper helper) { applyPerspective(helper); @@ -87,6 +109,8 @@ public class Camera { applyMode(helper); applyDirection(helper); applyPosition(helper); + + cacheCameraTransform(helper); } private void applyPerspective(WorldRenderHelper helper) { @@ -121,22 +145,48 @@ public class Camera { } private void applyDirection(WorldRenderHelper helper) { + float pitch = anchor.getCameraPitch(); + float yaw = anchor.getCameraYaw(); + helper.pushViewTransform() - .rotateY(-anchor.getCameraPitch()) - .rotateZ(-anchor.getCameraYaw()); + .rotateY(-pitch) + .rotateZ(-yaw); + + this.lastAnchorYaw = yaw; + this.lastAnchorPitch = pitch; + + this.lastAnchorLookingAt.set( + cos(pitch) * cos(yaw), + cos(pitch) * sin(yaw), + sin(pitch) + ); + this.lastAnchorUp.set( + cos(pitch + PI_F / 2) * cos(yaw), + cos(pitch + PI_F / 2) * sin(yaw), + sin(pitch + PI_F / 2) + ); } private void applyPosition(WorldRenderHelper helper) { Vec3 v = Vectors.grab3(); anchor.getCameraPosition(v); - v.negate(); + this.lastAnchorPosition.set(v); + v.negate(); helper.pushViewTransform().translate(v); Vectors.release(v); } + private void cacheCameraTransform(WorldRenderHelper helper) { + this.lastCameraMatrix.set(helper.getViewTransform()); + } + + /* + * FOV management + */ + private float computeFovY() { float widthOverHeight = GraphicsInterface.getAspectRatio(); @@ -159,6 +209,10 @@ public class Camera { this.fieldOfView = fieldOfView; } + /* + * Anchor management + */ + public Anchor getAnchor() { return anchor; } @@ -171,6 +225,7 @@ public class Camera { if (anchor == null) { this.anchor = null; this.modes = null; + invalidateCache(); return; } @@ -189,6 +244,22 @@ public class Camera { this.currentModeIndex = 0; } + private void invalidateCache() { + this.lastAnchorPosition.set(Float.NaN); + this.lastAnchorYaw = Float.NaN; + this.lastAnchorPitch = Float.NaN; + + this.lastCameraMatrix.set( + Float.NaN, Float.NaN, Float.NaN, Float.NaN, + Float.NaN, Float.NaN, Float.NaN, Float.NaN, + Float.NaN, Float.NaN, Float.NaN, Float.NaN, + Float.NaN, Float.NaN, Float.NaN, Float.NaN + ); + + this.lastAnchorLookingAt.set(Float.NaN); + this.lastAnchorUp.set(Float.NaN); + } + public Anchor.Mode getMode() { return modes[currentModeIndex]; } @@ -201,4 +272,28 @@ public class Camera { } } + public float getLastAnchorYaw() { + return lastAnchorYaw; + } + + public float getLastAnchorPitch() { + return lastAnchorPitch; + } + + public Vec3 getLastAnchorPosition() { + return lastAnchorPosition; + } + + public Mat4 getLastCameraMatrix() { + return lastCameraMatrix; + } + + public Vec3 getLastAnchorLookingAt() { + return lastAnchorLookingAt; + } + + public Vec3 getLastAnchorUp() { + return lastAnchorUp; + } + } diff --git a/src/main/java/ru/windcorp/progressia/common/util/FloatMathUtils.java b/src/main/java/ru/windcorp/progressia/common/util/FloatMathUtils.java index bdf3dec..89449ab 100644 --- a/src/main/java/ru/windcorp/progressia/common/util/FloatMathUtils.java +++ b/src/main/java/ru/windcorp/progressia/common/util/FloatMathUtils.java @@ -14,6 +14,18 @@ public class FloatMathUtils { return a - 2*PI_F * floor((a + PI_F) / (2*PI_F)); } + public static float sin(float x) { + return (float) Math.sin(x); + } + + public static float cos(float x) { + return (float) Math.cos(x); + } + + public static float tan(float x) { + return (float) Math.tan(x); + } + private FloatMathUtils() {} }