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/client/graphics/world/Camera.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java index 1001921..853f5e7 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; @@ -76,11 +77,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) { SoundManager.update(); @@ -90,6 +112,8 @@ public class Camera { applyMode(helper); applyDirection(helper); applyPosition(helper); + + cacheCameraTransform(helper); } private void applyPerspective(WorldRenderHelper helper) { @@ -124,22 +148,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(); @@ -162,6 +212,10 @@ public class Camera { this.fieldOfView = fieldOfView; } + /* + * Anchor management + */ + public Anchor getAnchor() { return anchor; } @@ -174,6 +228,7 @@ public class Camera { if (anchor == null) { this.anchor = null; this.modes = null; + invalidateCache(); return; } @@ -192,6 +247,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]; } @@ -204,4 +275,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/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/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() {} } 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(); + } + }; + } + +}