diff --git a/src/main/java/ru/windcorp/progressia/common/state/AbstractStatefulObjectLayout.java b/src/main/java/ru/windcorp/progressia/common/state/AbstractStatefulObjectLayout.java
index b2feb80..c00e1de 100644
--- a/src/main/java/ru/windcorp/progressia/common/state/AbstractStatefulObjectLayout.java
+++ b/src/main/java/ru/windcorp/progressia/common/state/AbstractStatefulObjectLayout.java
@@ -28,6 +28,20 @@ public abstract class AbstractStatefulObjectLayout
public AbstractStatefulObjectLayout(String objectId) {
super(objectId);
}
+
+ @Override
+ public StateStorage createStorage() {
+ StateStorage storage = instantiateStorage();
+
+ int fieldCount = getFieldCount();
+ for (int i = 0; i < fieldCount; ++i) {
+ getField(i).setDefault(storage);
+ }
+
+ return storage;
+ }
+
+ protected abstract StateStorage instantiateStorage();
protected abstract int getFieldCount();
diff --git a/src/main/java/ru/windcorp/progressia/common/state/Encodable.java b/src/main/java/ru/windcorp/progressia/common/state/Encodable.java
new file mode 100644
index 0000000..670a338
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/state/Encodable.java
@@ -0,0 +1,64 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.common.state;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public interface Encodable {
+
+ /**
+ * 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}
+ */
+ void read(DataInput input, IOContext context) throws IOException;
+
+ /**
+ * 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}
+ */
+ void write(DataOutput output, IOContext context) throws IOException;
+
+ /**
+ * Turns {@code destination} into a deep copy of this object.
+ *
+ * Changes the provided object so that:
+ *
+ * the provided object equals this 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. Runtime class
+ * must match this class
+ * @return {@code destination}
+ */
+ void copy(Encodable destination);
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/state/HashMapStateStorage.java b/src/main/java/ru/windcorp/progressia/common/state/HashMapStateStorage.java
index 52d446f..8a1afc0 100644
--- a/src/main/java/ru/windcorp/progressia/common/state/HashMapStateStorage.java
+++ b/src/main/java/ru/windcorp/progressia/common/state/HashMapStateStorage.java
@@ -19,11 +19,14 @@
package ru.windcorp.progressia.common.state;
import gnu.trove.map.TIntIntMap;
+import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntIntHashMap;
+import gnu.trove.map.hash.TIntObjectHashMap;
public class HashMapStateStorage extends StateStorage {
private final TIntIntMap ints = new TIntIntHashMap();
+ private final TIntObjectMap objects = new TIntObjectHashMap<>();
@Override
public int getInt(int index) {
@@ -34,5 +37,15 @@ public class HashMapStateStorage extends StateStorage {
public void setInt(int index, int value) {
ints.put(index, value);
}
+
+ @Override
+ public Object getObject(int index) {
+ return objects.get(index);
+ }
+
+ @Override
+ public void setObject(int index, Object object) {
+ objects.put(index, object);
+ }
}
diff --git a/src/main/java/ru/windcorp/progressia/common/state/InspectingStatefulObjectLayout.java b/src/main/java/ru/windcorp/progressia/common/state/InspectingStatefulObjectLayout.java
index 8b0a9fe..d45b6b8 100644
--- a/src/main/java/ru/windcorp/progressia/common/state/InspectingStatefulObjectLayout.java
+++ b/src/main/java/ru/windcorp/progressia/common/state/InspectingStatefulObjectLayout.java
@@ -20,6 +20,9 @@ package ru.windcorp.progressia.common.state;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Supplier;
+
+import ru.windcorp.progressia.common.state.codec.ObjectCodec;
public class InspectingStatefulObjectLayout
extends AbstractStatefulObjectLayout {
@@ -33,7 +36,7 @@ public class InspectingStatefulObjectLayout
}
@Override
- public StateStorage createStorage() {
+ public StateStorage instantiateStorage() {
return new HashMapStateStorage();
}
@@ -81,6 +84,31 @@ public class InspectingStatefulObjectLayout
}
}
+
+ private class Obj implements StateFieldBuilder.Obj {
+
+ private final ObjectCodec codec;
+ private final Supplier defaultValue;
+
+ public Obj(ObjectCodec codec, Supplier defaultValue) {
+ this.codec = codec;
+ this.defaultValue = defaultValue;
+ }
+
+ @Override
+ public ObjectStateField build() {
+ return registerField(
+ new ObjectStateField(
+ id,
+ isLocal,
+ fieldIndexCounters.getObjectsThenIncrement(),
+ codec,
+ defaultValue
+ )
+ );
+ }
+
+ }
private final String id;
@@ -94,6 +122,11 @@ public class InspectingStatefulObjectLayout
public Int ofInt() {
return new Int();
}
+
+ @Override
+ public Obj of(ObjectCodec codec, Supplier defaultValue) {
+ return new Obj(codec, defaultValue);
+ }
@Override
public StateFieldBuilder setLocal(boolean isLocal) {
diff --git a/src/main/java/ru/windcorp/progressia/common/state/IntStateField.java b/src/main/java/ru/windcorp/progressia/common/state/IntStateField.java
index abf8573..3b550c5 100644
--- a/src/main/java/ru/windcorp/progressia/common/state/IntStateField.java
+++ b/src/main/java/ru/windcorp/progressia/common/state/IntStateField.java
@@ -78,5 +78,10 @@ public class IntStateField extends StateField {
public boolean areEqual(StatefulObject a, StatefulObject b) {
return get(a) == get(b);
}
+
+ @Override
+ public void setDefault(StateStorage storage) {
+ storage.setInt(getIndex(), 0);
+ }
}
diff --git a/src/main/java/ru/windcorp/progressia/common/state/ObjectStateField.java b/src/main/java/ru/windcorp/progressia/common/state/ObjectStateField.java
new file mode 100644
index 0000000..823ce64
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/state/ObjectStateField.java
@@ -0,0 +1,107 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package ru.windcorp.progressia.common.state;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.function.Supplier;
+
+import ru.windcorp.progressia.common.state.codec.ObjectCodec;
+
+public class ObjectStateField extends StateField {
+
+ private final ObjectCodec codec;
+ private final Supplier defaultValue;
+
+ public ObjectStateField(
+ String id,
+ boolean isLocal,
+ int index,
+ ObjectCodec codec,
+ Supplier defaultValue
+ ) {
+ super(id, isLocal, index);
+
+ this.codec = codec;
+ this.defaultValue = defaultValue;
+ }
+
+ public ObjectCodec getCodec() {
+ return codec;
+ }
+
+ @SuppressWarnings("unchecked")
+ public T get(StatefulObject object) {
+ return (T) object.getStorage().getObject(getIndex());
+ }
+
+ public void setNow(StatefulObject object, T value) {
+ object.getStorage().setObject(getIndex(), value);
+ }
+
+ public void set(StateChanger changer, T value) {
+ changer.setObject(this, value);
+ }
+
+ @Override
+ public void read(
+ StatefulObject object,
+ DataInput input,
+ IOContext context
+ )
+ throws IOException {
+
+ T previous = get(object);
+ T result = codec.read(previous, input, context);
+ object.getStorage().setObject(getIndex(), result);
+ }
+
+ @Override
+ public void write(
+ StatefulObject object,
+ DataOutput output,
+ IOContext context
+ )
+ throws IOException {
+
+ codec.write(object.getStorage().getObject(getIndex()), output, context);
+ }
+
+ @Override
+ public void copy(StatefulObject from, StatefulObject to) {
+ setNow(to, get(from));
+ }
+
+ @Override
+ public int computeHashCode(StatefulObject object) {
+ return codec.computeHashCode(get(object));
+ }
+
+ @Override
+ public boolean areEqual(StatefulObject a, StatefulObject b) {
+ return codec.areEqual(get(a), get(b));
+ }
+
+ @Override
+ public void setDefault(StateStorage storage) {
+ storage.setObject(getIndex(), defaultValue.get());
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/state/OptimizedStateStorage.java b/src/main/java/ru/windcorp/progressia/common/state/OptimizedStateStorage.java
index 478fbac..0d75f61 100644
--- a/src/main/java/ru/windcorp/progressia/common/state/OptimizedStateStorage.java
+++ b/src/main/java/ru/windcorp/progressia/common/state/OptimizedStateStorage.java
@@ -21,9 +21,11 @@ package ru.windcorp.progressia.common.state;
public class OptimizedStateStorage extends StateStorage {
private final int[] ints;
+ private final Object[] objects;
public OptimizedStateStorage(PrimitiveCounters sizes) {
this.ints = new int[sizes.getInts()];
+ this.objects = new Object[sizes.getObjects()];
}
@Override
@@ -35,5 +37,15 @@ public class OptimizedStateStorage extends StateStorage {
public void setInt(int index, int value) {
ints[index] = value;
}
+
+ @Override
+ public Object getObject(int index) {
+ return objects[index];
+ }
+
+ @Override
+ public void setObject(int index, Object object) {
+ objects[index] = object;
+ }
}
diff --git a/src/main/java/ru/windcorp/progressia/common/state/OptimizedStatefulObjectLayout.java b/src/main/java/ru/windcorp/progressia/common/state/OptimizedStatefulObjectLayout.java
index 1158ab0..14152f4 100644
--- a/src/main/java/ru/windcorp/progressia/common/state/OptimizedStatefulObjectLayout.java
+++ b/src/main/java/ru/windcorp/progressia/common/state/OptimizedStatefulObjectLayout.java
@@ -19,9 +19,12 @@
package ru.windcorp.progressia.common.state;
import java.util.List;
+import java.util.function.Supplier;
import com.google.common.collect.ImmutableList;
+import ru.windcorp.progressia.common.state.codec.ObjectCodec;
+
public class OptimizedStatefulObjectLayout
extends AbstractStatefulObjectLayout {
@@ -49,7 +52,7 @@ public class OptimizedStatefulObjectLayout
}
@Override
- public StateStorage createStorage() {
+ public StateStorage instantiateStorage() {
return new OptimizedStateStorage(sizes);
}
@@ -71,6 +74,17 @@ public class OptimizedStatefulObjectLayout
}
};
}
+
+ @Override
+ public Obj of(ObjectCodec codec, Supplier defaultValue) {
+ return new Obj() {
+ @SuppressWarnings("unchecked")
+ @Override
+ public ObjectStateField build() {
+ return (ObjectStateField) result;
+ }
+ };
+ }
@Override
public StateFieldBuilder setLocal(boolean isLocal) {
diff --git a/src/main/java/ru/windcorp/progressia/common/state/PrimitiveCounters.java b/src/main/java/ru/windcorp/progressia/common/state/PrimitiveCounters.java
index d3e2dbb..09c71a8 100644
--- a/src/main/java/ru/windcorp/progressia/common/state/PrimitiveCounters.java
+++ b/src/main/java/ru/windcorp/progressia/common/state/PrimitiveCounters.java
@@ -21,12 +21,14 @@ package ru.windcorp.progressia.common.state;
class PrimitiveCounters {
private int ints = 0;
+ private int objects = 0;
public PrimitiveCounters() {
}
public PrimitiveCounters(PrimitiveCounters copyFrom) {
this.ints = copyFrom.ints;
+ this.objects = copyFrom.objects;
}
public int getInts() {
@@ -36,5 +38,13 @@ class PrimitiveCounters {
public int getIntsThenIncrement() {
return this.ints++;
}
+
+ public int getObjects() {
+ return objects;
+ }
+
+ public int getObjectsThenIncrement() {
+ return this.objects++;
+ }
}
diff --git a/src/main/java/ru/windcorp/progressia/common/state/StateChanger.java b/src/main/java/ru/windcorp/progressia/common/state/StateChanger.java
index cc8cd5a..f68ba43 100644
--- a/src/main/java/ru/windcorp/progressia/common/state/StateChanger.java
+++ b/src/main/java/ru/windcorp/progressia/common/state/StateChanger.java
@@ -21,5 +21,6 @@ package ru.windcorp.progressia.common.state;
public interface StateChanger {
void setInt(IntStateField field, int value);
+ void setObject(ObjectStateField field, T 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
index ca88c4f..0fe3562 100644
--- a/src/main/java/ru/windcorp/progressia/common/state/StateField.java
+++ b/src/main/java/ru/windcorp/progressia/common/state/StateField.java
@@ -66,5 +66,7 @@ public abstract class StateField extends Namespaced {
public abstract int computeHashCode(StatefulObject object);
public abstract boolean areEqual(StatefulObject a, StatefulObject b);
+
+ public abstract void setDefault(StateStorage storage);
}
diff --git a/src/main/java/ru/windcorp/progressia/common/state/StateFieldBuilder.java b/src/main/java/ru/windcorp/progressia/common/state/StateFieldBuilder.java
index 97c7176..05a877d 100644
--- a/src/main/java/ru/windcorp/progressia/common/state/StateFieldBuilder.java
+++ b/src/main/java/ru/windcorp/progressia/common/state/StateFieldBuilder.java
@@ -18,13 +18,58 @@
package ru.windcorp.progressia.common.state;
+import java.util.function.Supplier;
+
+import ru.windcorp.progressia.common.state.codec.ObjectCodec;
+import ru.windcorp.progressia.common.state.codec.ObjectCodecRegistry;
+
public interface StateFieldBuilder {
public static interface Int {
IntStateField build();
}
+
+ public static interface Obj {
+ ObjectStateField build();
+ }
Int ofInt();
+
+ Obj of(ObjectCodec codec, Supplier defaultValue);
+
+ default Obj of(Class clazz, Supplier defaultValue) {
+ ObjectCodec codec = ObjectCodecRegistry.get(clazz);
+ return of(codec, defaultValue);
+ }
+
+ default Obj of(ObjectCodec codec, T defaultValue) {
+ return of(codec, (Supplier) () -> codec.copy(defaultValue, null));
+ }
+
+ default Obj of(Class clazz, T defaultValue) {
+ ObjectCodec codec = ObjectCodecRegistry.get(clazz);
+ return of(codec, (Supplier) () -> codec.copy(defaultValue, null));
+ }
+
+ default Obj of(ObjectCodec codec) {
+ return of(codec, (Supplier) () -> null);
+ }
+
+ default Obj of(Class clazz) {
+ ObjectCodec codec = ObjectCodecRegistry.get(clazz);
+ return of(codec, (Supplier) () -> null);
+ }
+
+ @SuppressWarnings("unchecked")
+ default Obj def(Supplier defaultValue) {
+ Class clazz = (Class) defaultValue.get().getClass();
+ return of(clazz, defaultValue);
+ }
+
+ @SuppressWarnings("unchecked")
+ default Obj def(T defaultValue) {
+ return of((Class) defaultValue.getClass(), defaultValue);
+ }
StateFieldBuilder setLocal(boolean isLocal);
diff --git a/src/main/java/ru/windcorp/progressia/common/state/StateStorage.java b/src/main/java/ru/windcorp/progressia/common/state/StateStorage.java
index 2f999f0..7b0ee5e 100644
--- a/src/main/java/ru/windcorp/progressia/common/state/StateStorage.java
+++ b/src/main/java/ru/windcorp/progressia/common/state/StateStorage.java
@@ -23,5 +23,9 @@ public abstract class StateStorage {
public abstract int getInt(int index);
public abstract void setInt(int index, int value);
+
+ public abstract Object getObject(int index);
+
+ public abstract void setObject(int index, Object object);
}
diff --git a/src/main/java/ru/windcorp/progressia/common/state/StatefulObject.java b/src/main/java/ru/windcorp/progressia/common/state/StatefulObject.java
index 39f5b58..2feb7e6 100644
--- a/src/main/java/ru/windcorp/progressia/common/state/StatefulObject.java
+++ b/src/main/java/ru/windcorp/progressia/common/state/StatefulObject.java
@@ -52,7 +52,7 @@ import ru.windcorp.progressia.common.util.namespaces.Namespaced;
* type.
*
*/
-public abstract class StatefulObject extends Namespaced {
+public abstract class StatefulObject extends Namespaced implements Encodable {
private final StatefulObjectLayout layout;
@@ -133,6 +133,7 @@ public abstract class StatefulObject extends Namespaced {
* @throws IOException if the state is encoded poorly or an error occurs
* in {@code input}
*/
+ @Override
public void read(DataInput input, IOContext context) throws IOException {
getLayout().read(this, input, context);
}
@@ -145,6 +146,7 @@ public abstract class StatefulObject extends Namespaced {
* @param context the context
* @throws IOException if an error occurs in {@code output}
*/
+ @Override
public void write(DataOutput output, IOContext context) throws IOException {
getLayout().write(this, output, context);
}
@@ -166,7 +168,8 @@ public abstract class StatefulObject extends Namespaced {
*
* @param destination the object to copy this object into.
*/
- public StatefulObject copy(StatefulObject destination) {
+ @Override
+ public void copy(Encodable destination) {
Objects.requireNonNull(destination, "destination");
if (destination == this) {
@@ -182,8 +185,7 @@ public abstract class StatefulObject extends Namespaced {
);
}
- getLayout().copy(this, destination);
- return destination;
+ getLayout().copy(this, (StatefulObject) destination);
}
/**
diff --git a/src/main/java/ru/windcorp/progressia/common/state/codec/EncodableCodec.java b/src/main/java/ru/windcorp/progressia/common/state/codec/EncodableCodec.java
new file mode 100644
index 0000000..0ff9f1e
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/state/codec/EncodableCodec.java
@@ -0,0 +1,52 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.common.state.codec;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Objects;
+
+import ru.windcorp.progressia.common.state.Encodable;
+import ru.windcorp.progressia.common.state.IOContext;
+
+public class EncodableCodec extends ReusableObjectCodec {
+
+ public EncodableCodec() {
+ super(Encodable.class);
+ }
+
+ @Override
+ protected Encodable doRead(Encodable previous, DataInput input, IOContext context) throws IOException {
+ previous.read(input, context);
+ return previous;
+ }
+
+ @Override
+ protected void doWrite(Encodable obj, DataOutput output, IOContext context) throws IOException {
+ obj.write(output, context);
+ }
+
+ @Override
+ protected Encodable doCopy(Encodable object, Encodable previous) {
+ Objects.requireNonNull(previous, "previous");
+ object.copy(previous);
+ return previous;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/state/codec/ImmutableObjectCodec.java b/src/main/java/ru/windcorp/progressia/common/state/codec/ImmutableObjectCodec.java
new file mode 100644
index 0000000..64b30a7
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/state/codec/ImmutableObjectCodec.java
@@ -0,0 +1,43 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.common.state.codec;
+
+import java.io.DataInput;
+import java.io.IOException;
+
+import ru.windcorp.progressia.common.state.IOContext;
+
+public abstract class ImmutableObjectCodec extends ObjectCodec {
+
+ public ImmutableObjectCodec(Class clazz) {
+ super(clazz);
+ }
+
+ @Override
+ public final T copy(T object, T previous) {
+ return object;
+ }
+
+ @Override
+ protected final T doRead(T previous, DataInput input, IOContext context) throws IOException {
+ return doRead(input, context);
+ }
+
+ protected abstract T doRead(DataInput input, IOContext context) throws IOException;
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/state/codec/ObjectCodec.java b/src/main/java/ru/windcorp/progressia/common/state/codec/ObjectCodec.java
new file mode 100644
index 0000000..e5d363c
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/state/codec/ObjectCodec.java
@@ -0,0 +1,73 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.common.state.codec;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Objects;
+
+import ru.windcorp.progressia.common.state.IOContext;
+
+public abstract class ObjectCodec {
+
+ private final Class clazz;
+
+ public ObjectCodec(Class clazz) {
+ this.clazz = clazz;
+ }
+
+ public Class getDataType() {
+ return clazz;
+ }
+
+ @SuppressWarnings("unchecked")
+ public final T read(Object previous, DataInput input, IOContext context) throws IOException {
+ assert previous == null || clazz.isInstance(previous)
+ : "Cannot use codec " + this + " on object of type " + previous.getClass();
+
+ T result = doRead((T) previous, input, context);
+
+ assert result == null || clazz.isInstance(previous)
+ : "Codec " + this + " read object of type " + previous.getClass();
+ return result;
+ }
+
+ protected abstract T doRead(T previous, DataInput input, IOContext context) throws IOException;
+
+ @SuppressWarnings("unchecked")
+ public final void write(Object value, DataOutput output, IOContext context) throws IOException {
+ assert value == null || clazz.isInstance(value)
+ : "Cannot use codec " + this + " on object of type " + value.getClass();
+
+ doWrite((T) value, output, context);
+ }
+
+ protected abstract void doWrite(T obj, DataOutput output, IOContext context) throws IOException;
+
+ public abstract T copy(T object, T previous);
+
+ public int computeHashCode(T object) {
+ return Objects.hashCode(object);
+ }
+
+ public boolean areEqual(T a, T b) {
+ return Objects.equals(a, b);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/state/codec/ObjectCodecRegistry.java b/src/main/java/ru/windcorp/progressia/common/state/codec/ObjectCodecRegistry.java
new file mode 100644
index 0000000..582571d
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/state/codec/ObjectCodecRegistry.java
@@ -0,0 +1,49 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.common.state.codec;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import ru.windcorp.progressia.common.state.Encodable;
+
+public class ObjectCodecRegistry {
+
+ private static final Map, ObjectCodec>> CODECS = Collections.synchronizedMap(new HashMap<>());
+ private static final EncodableCodec ENCODABLE_FALLBACK = new EncodableCodec();
+
+ public static void register(ObjectCodec> codec) {
+ CODECS.put(codec.getDataType(), codec);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static ObjectCodec get(Class clazz) {
+ ObjectCodec> codec = CODECS.get(clazz);
+ if (codec != null) {
+ return (ObjectCodec) codec;
+ }
+
+ if (Encodable.class.isAssignableFrom(clazz)) {
+ return (ObjectCodec) ENCODABLE_FALLBACK;
+ }
+
+ throw new IllegalArgumentException("No codec registered for class " + clazz);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/state/codec/ReusableObjectCodec.java b/src/main/java/ru/windcorp/progressia/common/state/codec/ReusableObjectCodec.java
new file mode 100644
index 0000000..bb70fcb
--- /dev/null
+++ b/src/main/java/ru/windcorp/progressia/common/state/codec/ReusableObjectCodec.java
@@ -0,0 +1,42 @@
+/*
+ * Progressia
+ * Copyright (C) 2020-2021 Wind Corporation and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ru.windcorp.progressia.common.state.codec;
+
+public abstract class ReusableObjectCodec extends ObjectCodec {
+
+ public ReusableObjectCodec(Class clazz) {
+ super(clazz);
+ }
+
+ @Override
+ public final T copy(T object, T previous) {
+ if (object == null) {
+ return null;
+ }
+
+ T result = doCopy(object, previous);
+
+ assert result != null : "copy() returned null";
+ assert areEqual(object, result) : "copy() does not equal original: " + result + " != " + object;
+
+ return result;
+ }
+
+ protected abstract T doCopy(T object, T previous);
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java b/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java
index 3f33e50..a8a0386 100644
--- a/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java
+++ b/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java
@@ -199,6 +199,63 @@ public class VectorUtil {
return rotateOnly(inOut, mat, inOut);
}
+ public static Mat4 lookAt(Mat4 applyTo, Vec3 target, Vec3 up, Mat4 output) {
+ if (output == null) {
+ output = new Mat4();
+ }
+
+ Mat4 lookAtTransform = Matrices.grab4();
+
+ // Adapted from Glm.lookAt - we use Z as up
+
+ // f(normalize(target))
+ float fX = target.x;
+ float fY = target.y;
+ float fZ = target.z;
+ float inverseSqrt = 1f / (float) Math.sqrt(fX * fX + fY * fY + fZ * fZ);
+ fX *= inverseSqrt;
+ fY *= inverseSqrt;
+ fZ *= inverseSqrt;
+ // s(normalize(cross(up, f)))
+ float sX = up.y * fZ - up.z * fY;
+ float sY = up.z * fX - up.x * fZ;
+ float sZ = up.x * fY - up.y * fX;
+ inverseSqrt = 1.0f / (float) Math.sqrt(sX * sX + sY * sY + sZ * sZ);
+ sX *= inverseSqrt;
+ sY *= inverseSqrt;
+ sZ *= inverseSqrt;
+ // u(cross(f, s))
+ float uX = fY * sZ - fZ * sY;
+ float uY = fZ * sX - fX * sZ;
+ float uZ = fX * sY - fY * sX;
+
+ lookAtTransform.m00 = fX;
+ lookAtTransform.m01 = fY;
+ lookAtTransform.m02 = fZ;
+ lookAtTransform.m03 = 0f;
+ lookAtTransform.m10 = sX;
+ lookAtTransform.m11 = sY;
+ lookAtTransform.m12 = sZ;
+ lookAtTransform.m13 = 0f;
+ lookAtTransform.m20 = uX;
+ lookAtTransform.m21 = uY;
+ lookAtTransform.m22 = uZ;
+ lookAtTransform.m23 = 0f;
+ lookAtTransform.m30 = 0;
+ lookAtTransform.m31 = 0;
+ lookAtTransform.m32 = 0;
+ lookAtTransform.m33 = 1f;
+
+ applyTo.mul(lookAtTransform, output);
+ Matrices.release(lookAtTransform);
+
+ return output;
+ }
+
+ public static Mat4 lookAt(Vec3 center, Vec3 up, Mat4 inOut) {
+ return lookAt(inOut, center, up, inOut);
+ }
+
public static Vec3 rotate(Vec3 in, Vec3 axis, float angle, Vec3 out) {
if (out == null) {
out = new Vec3();
@@ -269,6 +326,20 @@ public class VectorUtil {
public static Vec3 projectOnVector(Vec3 inOut, Vec3 vector) {
return projectOnVector(inOut, vector);
}
+
+ public static float distanceSq(Vec3 a, Vec3 b) {
+ float x = a.x - b.x;
+ float y = a.y - b.y;
+ float z = a.z - b.z;
+ return x * x + y * y + z * z;
+ }
+
+ public static float distance(Vec3 a, Vec3 b) {
+ float x = a.x - b.x;
+ float y = a.y - b.y;
+ float z = a.z - b.z;
+ return (float) Math.sqrt(x * x + y * y + z * z);
+ }
public static Vec3 linearCombination(
Vec3 va,
diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldGenericContextRO.java b/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldGenericContextRO.java
index ec188d1..1001708 100644
--- a/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldGenericContextRO.java
+++ b/src/main/java/ru/windcorp/progressia/common/world/generic/context/WorldGenericContextRO.java
@@ -19,8 +19,11 @@ package ru.windcorp.progressia.common.world.generic.context;
import java.util.Collection;
import java.util.function.Consumer;
+import java.util.function.Predicate;
+import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
+import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.world.context.Context;
import ru.windcorp.progressia.common.world.generic.*;
import ru.windcorp.progressia.common.world.rels.RelFace;
@@ -141,16 +144,52 @@ public interface WorldGenericContextRO<
*/
E getEntity(long entityId);
+ /*
+ * Convenience methods
+ */
+
+ default void forEachEntity(Consumer action) {
+ getEntities().forEach(action);
+ }
+
+ default E findClosestEntity(Vec3 location, Predicate filter, float maxDistance) {
+ if (maxDistance <= 0) {
+ return null;
+ }
+
+ E result = getEntities().stream().filter(filter).min((a, b) -> {
+ float aDistance = VectorUtil.distanceSq(location, a.getPosition());
+ float bDistance = VectorUtil.distanceSq(location, b.getPosition());
+ return Float.compare(aDistance, bDistance);
+ }).orElse(null);
+
+ if (result == null) {
+ return null;
+ }
+ if (Float.isInfinite(maxDistance)) {
+ return result;
+ }
+ if (VectorUtil.distanceSq(location, result.getPosition()) > maxDistance * maxDistance) {
+ return null;
+ }
+
+ return result;
+ }
+
+ default E findClosestEntity(Vec3 location, float maxDistance) {
+ return findClosestEntity(location, e -> true, maxDistance);
+ }
+
/*
* Subcontexting
*/
-
+
@Override
BlockGenericContextRO push(Vec3i location);
-
+
@Override
TileStackGenericContextRO push(Vec3i location, RelFace face);
-
+
@Override
TileGenericContextRO push(Vec3i location, RelFace face, int layer);