mirror of
https://github.com/Anuken/Mindustry.git
synced 2026-09-22 13:01:12 +03:00
allow memory blocks to store any object (#12459)
* allow memory blocks to store any object This changes the way memory blocks are stored in save files. * fix memory block contents not being restored `afterReadAll()` seems to require `update` being set to `true` for being called. * add myself as a contributor * reduce boxing when reading/writing memory buildings This includes some changes to `TypeIO` to prevent duplicating its reading/writing logic. * Update TypeIO.java * Update MemoryBlock.java * Update TypeIO.java * Apply suggestion from @Anuken * Apply suggestion from @Anuken --------- Co-authored-by: Anuken <arnukren@gmail.com>
This commit is contained in:
@@ -197,3 +197,4 @@ Denilseven
|
||||
Alon
|
||||
硫缺铅/PyratiteNoLead
|
||||
Space
|
||||
Eric Wolf
|
||||
|
||||
@@ -39,115 +39,142 @@ import static mindustry.Vars.*;
|
||||
@SuppressWarnings("unused")
|
||||
@TypeIOHandler
|
||||
public class TypeIO{
|
||||
/** Possible Types of written objects. */
|
||||
public static final byte
|
||||
nullType = 0,
|
||||
integerType = 1,
|
||||
longType = 2,
|
||||
floatType = 3,
|
||||
stringType = 4,
|
||||
contentType = 5,
|
||||
intSeqType = 6,
|
||||
point2Type = 7,
|
||||
point2ArrayType = 8,
|
||||
techNodeType = 9,
|
||||
booleanType = 10,
|
||||
doubleType = 11,
|
||||
buildingType = 12,
|
||||
lAccessType = 13,
|
||||
byteArrayType = 14,
|
||||
legacyType = 15,
|
||||
booleanArrayType = 16,
|
||||
unitType = 17,
|
||||
vec2ArrayType = 18,
|
||||
vec2Type = 19,
|
||||
teamType = 20,
|
||||
intArrayType = 21,
|
||||
objectArrayType = 22,
|
||||
unitCommandType = 23;
|
||||
|
||||
private static final int maxArraySize = 1000, maxByteArraySize = 40_000, maxSyncedPlans = 20;
|
||||
|
||||
/** Write an object, prepending its {@code ObjectType}. */
|
||||
public static void writeObject(Writes write, Object object){
|
||||
if(object == null){
|
||||
write.b((byte)0);
|
||||
write.b(nullType);
|
||||
}else if(object instanceof Integer i){
|
||||
write.b((byte)1);
|
||||
write.b(integerType);
|
||||
write.i(i);
|
||||
}else if(object instanceof Long l){
|
||||
write.b((byte)2);
|
||||
write.b(longType);
|
||||
write.l(l);
|
||||
}else if(object instanceof Float f){
|
||||
write.b((byte)3);
|
||||
write.b(floatType);
|
||||
write.f(f);
|
||||
}else if(object instanceof String s){
|
||||
write.b((byte)4);
|
||||
write.b(stringType);
|
||||
writeString(write, s);
|
||||
}else if(object instanceof Content map){
|
||||
write.b((byte)5);
|
||||
write.b(contentType);
|
||||
write.b((byte)map.getContentType().ordinal());
|
||||
write.s(map.id);
|
||||
}else if(object instanceof IntSeq arr){
|
||||
if(arr.size > maxArraySize) throw new ArcRuntimeException("Array size too large: " + arr.size);
|
||||
write.b((byte)6);
|
||||
write.b(intSeqType);
|
||||
write.s((short)arr.size);
|
||||
for(int i = 0; i < arr.size; i++){
|
||||
write.i(arr.items[i]);
|
||||
}
|
||||
}else if(object instanceof Point2 p){
|
||||
write.b((byte)7);
|
||||
write.b(point2Type);
|
||||
write.i(p.x);
|
||||
write.i(p.y);
|
||||
}else if(object instanceof Point2[] p){
|
||||
//255 is the limit here, because it's a byte for some reason
|
||||
if(p.length > 255) throw new ArcRuntimeException("Array size too large: " + p.length);
|
||||
write.b((byte)8);
|
||||
write.b(point2ArrayType);
|
||||
write.b(p.length);
|
||||
for(Point2 point2 : p){
|
||||
write.i(point2.pack());
|
||||
}
|
||||
}else if(object instanceof TechNode map){
|
||||
write.b(9);
|
||||
write.b(techNodeType);
|
||||
write.b((byte)map.content.getContentType().ordinal());
|
||||
write.s(map.content.id);
|
||||
}else if(object instanceof Boolean b){
|
||||
write.b((byte)10);
|
||||
write.b(booleanType);
|
||||
write.bool(b);
|
||||
}else if(object instanceof Double d){
|
||||
write.b((byte)11);
|
||||
write.d(d);
|
||||
writeObject(write, d.doubleValue());
|
||||
}else if(object instanceof Building b){
|
||||
write.b(12);
|
||||
write.b(buildingType);
|
||||
write.i(b.pos());
|
||||
}else if(object instanceof BuildingBox b){
|
||||
write.b(12);
|
||||
write.b(buildingType);
|
||||
write.i(b.pos);
|
||||
}else if(object instanceof LAccess l){
|
||||
write.b((byte)13);
|
||||
write.b(lAccessType);
|
||||
write.s(l.ordinal());
|
||||
}else if(object instanceof byte[] b){
|
||||
if(b.length > maxByteArraySize) throw new ArcRuntimeException("Array size too large: " + b.length);
|
||||
write.b((byte)14);
|
||||
write.b(byteArrayType);
|
||||
write.i(b.length);
|
||||
write.b(b);
|
||||
}else if(object instanceof boolean[] b){
|
||||
if(b.length > maxArraySize) throw new ArcRuntimeException("Array size too large: " + b.length);
|
||||
|
||||
write.b(16);
|
||||
write.b(booleanArrayType);
|
||||
write.i(b.length);
|
||||
for(boolean bool : b){
|
||||
write.bool(bool);
|
||||
}
|
||||
}else if(object instanceof Unit u){
|
||||
write.b(17);
|
||||
write.b(unitType);
|
||||
write.i(u.id);
|
||||
}else if(object instanceof UnitBox u){
|
||||
write.b(17);
|
||||
write.b(unitType);
|
||||
write.i(u.id);
|
||||
}else if(object instanceof Vec2[] vecs){
|
||||
if(vecs.length > maxArraySize) throw new ArcRuntimeException("Array size too large: " + vecs.length);
|
||||
|
||||
write.b(18);
|
||||
write.b(vec2ArrayType);
|
||||
write.s(vecs.length);
|
||||
for(Vec2 v : vecs){
|
||||
write.f(v.x);
|
||||
write.f(v.y);
|
||||
}
|
||||
}else if(object instanceof Vec2 v){
|
||||
write.b((byte)19);
|
||||
write.b(vec2Type);
|
||||
write.f(v.x);
|
||||
write.f(v.y);
|
||||
}else if(object instanceof Team t){
|
||||
write.b((byte)20);
|
||||
write.b(teamType);
|
||||
write.b(t.id);
|
||||
}else if(object instanceof int[] i){
|
||||
if(i.length > maxArraySize) throw new ArcRuntimeException("Array size too large: " + i.length);
|
||||
|
||||
write.b((byte)21);
|
||||
write.b(intArrayType);
|
||||
writeInts(write, i);
|
||||
}else if(object instanceof Object[] objs){
|
||||
if(objs.length > maxArraySize) throw new ArcRuntimeException("Array size too large: " + objs.length);
|
||||
|
||||
write.b((byte)22);
|
||||
write.b(objectArrayType);
|
||||
write.i(objs.length);
|
||||
for(Object obj : objs){
|
||||
writeObject(write, obj);
|
||||
}
|
||||
}else if(object instanceof UnitCommand command){
|
||||
write.b(23);
|
||||
write.b(unitCommandType);
|
||||
write.s(command.id);
|
||||
}else if(object instanceof Bullet b || object instanceof Seq<?> s){ //write bullets as null
|
||||
write.b((byte)0);
|
||||
@@ -156,6 +183,12 @@ public class TypeIO{
|
||||
}
|
||||
}
|
||||
|
||||
/** Overload of {@code writeObject(Writes, Object)} which prevents boxing. */
|
||||
public static void writeObject(Writes write, double object){
|
||||
write.b(doubleType);
|
||||
write.d(object);
|
||||
}
|
||||
|
||||
public static @Nullable Object readObject(Reads read){
|
||||
return readObjectBoxed(read, false);
|
||||
}
|
||||
@@ -178,16 +211,20 @@ public class TypeIO{
|
||||
}
|
||||
|
||||
public static @Nullable Object readObject(Reads read, boolean box, @Nullable ContentMapper mapper, boolean safe, boolean allowArrays){
|
||||
byte type = read.b();
|
||||
return readObject(read, box, mapper, safe, allowArrays, type);
|
||||
}
|
||||
|
||||
public static @Nullable Object readObject(Reads read, boolean box, @Nullable ContentMapper mapper, boolean safe, boolean allowArrays, byte type){
|
||||
//use a much lower array size limit for build plans
|
||||
int maxArraySize = safe ? TypeIO.maxArraySize : 200;
|
||||
|
||||
byte type = read.b();
|
||||
return switch(type){
|
||||
case 0 -> null;
|
||||
case 1 -> read.i();
|
||||
case 2 -> read.l();
|
||||
case 3 -> read.f();
|
||||
case 4 -> {
|
||||
case nullType -> null;
|
||||
case integerType -> read.i();
|
||||
case longType -> read.l();
|
||||
case floatType -> read.f();
|
||||
case stringType -> {
|
||||
byte exists = read.b();
|
||||
if(exists != 0){
|
||||
//in a safe context, strings can only be 1200 chars
|
||||
@@ -196,8 +233,8 @@ public class TypeIO{
|
||||
yield null;
|
||||
}
|
||||
}
|
||||
case 5 -> mapper == null ? content.getByID(ContentType.all[read.b()], read.s()) : mapper.get(ContentType.all[read.b()], read.s());
|
||||
case 6 -> {
|
||||
case contentType -> mapper == null ? content.getByID(ContentType.all[read.b()], read.s()) : mapper.get(ContentType.all[read.b()], read.s());
|
||||
case intSeqType -> {
|
||||
if(!allowArrays) throw new RuntimeException("Nested arrays are not allowed");
|
||||
short len = read.s();
|
||||
if(len > maxArraySize) throw new RuntimeException("Invalid array size: " + len);
|
||||
@@ -205,20 +242,20 @@ public class TypeIO{
|
||||
for(int i = 0; i < len; i ++) arr.add(read.i());
|
||||
yield arr;
|
||||
}
|
||||
case 7 -> new Point2(read.i(), read.i());
|
||||
case 8 -> {
|
||||
case point2Type -> new Point2(read.i(), read.i());
|
||||
case point2ArrayType -> {
|
||||
if(!allowArrays) throw new RuntimeException("Nested arrays are not allowed");
|
||||
int len = read.ub();
|
||||
Point2[] out = new Point2[len];
|
||||
for(int i = 0; i < len; i ++) out[i] = Point2.unpack(read.i());
|
||||
yield out;
|
||||
}
|
||||
case 9 -> content.<UnlockableContent>getByID(ContentType.all[read.b()], read.s()).techNode;
|
||||
case 10 -> read.bool();
|
||||
case 11 -> read.d();
|
||||
case 12 -> !box ? world.build(read.i()) : new BuildingBox(read.i());
|
||||
case 13 -> LAccess.all[read.s()];
|
||||
case 14 -> {
|
||||
case techNodeType -> content.<UnlockableContent>getByID(ContentType.all[read.b()], read.s()).techNode;
|
||||
case booleanType -> read.bool();
|
||||
case doubleType -> read.d();
|
||||
case buildingType -> !box ? world.build(read.i()) : new BuildingBox(read.i());
|
||||
case lAccessType -> LAccess.all[read.s()];
|
||||
case byteArrayType -> {
|
||||
if(!allowArrays) throw new RuntimeException("Nested arrays are not allowed");
|
||||
int len = read.i();
|
||||
if(len > maxByteArraySize) throw new RuntimeException("Invalid array size: " + len);
|
||||
@@ -228,11 +265,11 @@ public class TypeIO{
|
||||
yield bytes;
|
||||
}
|
||||
//unit command
|
||||
case 15 -> {
|
||||
case legacyType -> {
|
||||
read.b();
|
||||
yield null;
|
||||
}
|
||||
case 16 -> {
|
||||
case booleanArrayType -> {
|
||||
if(!allowArrays) throw new RuntimeException("Nested arrays are not allowed");
|
||||
int len = read.i();
|
||||
if(len > maxArraySize) throw new RuntimeException("Invalid array size: " + len);
|
||||
@@ -241,8 +278,8 @@ public class TypeIO{
|
||||
for(int i = 0; i < len; i ++) bools[i] = read.bool();
|
||||
yield bools;
|
||||
}
|
||||
case 17 -> !box ? Groups.unit.getByID(read.i()) : new UnitBox(read.i());
|
||||
case 18 -> {
|
||||
case unitType -> !box ? Groups.unit.getByID(read.i()) : new UnitBox(read.i());
|
||||
case vec2ArrayType -> {
|
||||
if(!allowArrays) throw new RuntimeException("Nested arrays are not allowed");
|
||||
int len = read.s();
|
||||
if(len > maxArraySize) throw new RuntimeException("Invalid array size: " + len);
|
||||
@@ -251,10 +288,10 @@ public class TypeIO{
|
||||
for(int i = 0; i < len; i ++) out[i] = new Vec2(read.f(), read.f());
|
||||
yield out;
|
||||
}
|
||||
case 19 -> new Vec2(read.f(), read.f());
|
||||
case 20 -> Team.all[read.ub()];
|
||||
case 21 -> readInts(read);
|
||||
case 22 -> {
|
||||
case vec2Type -> new Vec2(read.f(), read.f());
|
||||
case teamType -> Team.all[read.ub()];
|
||||
case intArrayType -> readInts(read);
|
||||
case objectArrayType -> {
|
||||
if(!allowArrays) throw new RuntimeException("Nested arrays are not allowed");
|
||||
int len = read.i();
|
||||
if(len > maxArraySize) throw new RuntimeException("Invalid array size: " + len);
|
||||
@@ -265,7 +302,7 @@ public class TypeIO{
|
||||
}
|
||||
yield objs;
|
||||
}
|
||||
case 23 -> content.unitCommand(read.us());
|
||||
case unitCommandType -> content.unitCommand(read.us());
|
||||
default -> throw new IllegalArgumentException("Unknown object type: " + type);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package mindustry.world.blocks.logic;
|
||||
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.io.TypeIO.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class MemoryBlock extends Block{
|
||||
public int memoryCapacity = 32;
|
||||
|
||||
@@ -46,7 +51,19 @@ public class MemoryBlock extends Block{
|
||||
}
|
||||
|
||||
public class MemoryBuild extends Building implements LReadable, LWritable{
|
||||
public double[] memory = new double[memoryCapacity];
|
||||
/** Marks a memory slot as being stored in {@code numberMemory} (instead of {@code objectMemory}) */
|
||||
private static final Object sentinel = new Object();
|
||||
|
||||
/** Objects stored in this memory building */
|
||||
private Object[] objectMemory = new Object[memoryCapacity];
|
||||
|
||||
/** Numbers stored in this memory building */
|
||||
private double[] numberMemory = new double[memoryCapacity];
|
||||
|
||||
{
|
||||
//all memory slots contain 0 when first initialized
|
||||
Arrays.fill(objectMemory, sentinel);
|
||||
}
|
||||
|
||||
//massive byte size means picking up causes sync issues
|
||||
@Override
|
||||
@@ -73,7 +90,17 @@ public class MemoryBlock extends Block{
|
||||
public void read(LVar position, LVar output){
|
||||
int address = position.numi();
|
||||
//Return null when out of bounds. (instead of 0)
|
||||
output.setnum(address < 0 || address >= memory.length ? Double.NaN : memory[address]);
|
||||
if(address < 0 || address >= objectMemory.length){
|
||||
output.setobj(null);
|
||||
return;
|
||||
}
|
||||
|
||||
Object value = objectMemory[address];
|
||||
if(value == sentinel){
|
||||
output.setnum(numberMemory[address]);
|
||||
}else{
|
||||
output.setobj(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,8 +111,14 @@ public class MemoryBlock extends Block{
|
||||
@Override
|
||||
public void write(LVar position, LVar value){
|
||||
int address = position.numi();
|
||||
if(address < 0 || address >= memory.length) return;
|
||||
memory[address] = value.num();
|
||||
if(address < 0 || address >= objectMemory.length) return;
|
||||
|
||||
if(value.isobj){
|
||||
objectMemory[address] = value.objval;
|
||||
}else{
|
||||
objectMemory[address] = sentinel;
|
||||
numberMemory[address] = value.numval;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,13 +135,23 @@ public class MemoryBlock extends Block{
|
||||
super.damage(damage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte version(){
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Writes write){
|
||||
super.write(write);
|
||||
|
||||
write.i(memory.length);
|
||||
for(double v : memory){
|
||||
write.d(v);
|
||||
write.i(objectMemory.length);
|
||||
for(int i = 0; i < objectMemory.length; i++){
|
||||
Object value = objectMemory[i];
|
||||
if(value == sentinel){
|
||||
TypeIO.writeObject(write, numberMemory[i]);
|
||||
}else{
|
||||
TypeIO.writeObject(write, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,9 +160,46 @@ public class MemoryBlock extends Block{
|
||||
super.read(read, revision);
|
||||
|
||||
int amount = read.i();
|
||||
|
||||
if(revision == 0){
|
||||
for(int i = 0; i < amount; i++){
|
||||
double val = read.d();
|
||||
if(i < objectMemory.length){
|
||||
objectMemory[i] = sentinel;
|
||||
numberMemory[i] = val;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//read all data, but ignore anything not fitting inside this memory building
|
||||
for(int i = 0; i < amount; i++){
|
||||
double val = read.d();
|
||||
if(i < memory.length) memory[i] = val;
|
||||
byte type = read.b();
|
||||
if(type == TypeIO.doubleType){
|
||||
//same logic as readObject, but prevents boxing
|
||||
double value = read.d();
|
||||
if(i < objectMemory.length){
|
||||
objectMemory[i] = sentinel;
|
||||
numberMemory[i] = value;
|
||||
}
|
||||
}else{
|
||||
Object value = TypeIO.readObject(read, true, null, false, true, type);
|
||||
if(i < objectMemory.length){
|
||||
objectMemory[i] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterReadAll(){
|
||||
super.afterReadAll();
|
||||
//unbox any memory contents which require it
|
||||
for(int i = 0; i < objectMemory.length; i++){
|
||||
//skips sentinel objects
|
||||
if(objectMemory[i] instanceof Boxed<?> boxed){
|
||||
objectMemory[i] = boxed.unbox();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user