diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/AnimatedTextureCallback.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/AnimatedTextureCallback.java index 0af85de6c..50f81c382 100644 --- a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/AnimatedTextureCallback.java +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/AnimatedTextureCallback.java @@ -1,5 +1,6 @@ package com.shade.decima.rtti.callbacks; +import com.shade.decima.rtti.TypeFactory; import com.shade.decima.rtti.serde.ExtraBinaryDataCallback; import com.shade.util.NotNull; import com.shade.util.io.BinaryReader; @@ -11,6 +12,6 @@ public interface AnimatedTextureData { } @Override - public void deserialize(@NotNull BinaryReader reader, @NotNull AnimatedTextureData object) throws IOException { + public void deserialize(@NotNull BinaryReader reader, @NotNull TypeFactory factory, @NotNull AnimatedTextureData object) throws IOException { } } diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/CoreScriptCallback.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/CoreScriptCallback.java index 9eb662f67..f7a04d851 100644 --- a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/CoreScriptCallback.java +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/CoreScriptCallback.java @@ -1,5 +1,6 @@ package com.shade.decima.rtti.callbacks; +import com.shade.decima.rtti.TypeFactory; import com.shade.decima.rtti.serde.ExtraBinaryDataCallback; import com.shade.util.NotNull; import com.shade.util.io.BinaryReader; @@ -12,7 +13,7 @@ public interface LuaScript { } @Override - public void deserialize(@NotNull BinaryReader reader, @NotNull LuaScript object) throws IOException { + public void deserialize(@NotNull BinaryReader reader, @NotNull TypeFactory factory, @NotNull LuaScript object) throws IOException { var flags = reader.readInt(); if (flags != 0) { var name1 = reader.readString(reader.readInt()); diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/ExternalSourceCacheResourceCallback.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/ExternalSourceCacheResourceCallback.java index 89b917240..8db710c6a 100644 --- a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/ExternalSourceCacheResourceCallback.java +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/ExternalSourceCacheResourceCallback.java @@ -1,6 +1,7 @@ package com.shade.decima.rtti.callbacks; import com.shade.decima.rtti.RTTI; +import com.shade.decima.rtti.TypeFactory; import com.shade.decima.rtti.serde.ExtraBinaryDataCallback; import com.shade.util.NotNull; import com.shade.util.io.BinaryReader; @@ -30,12 +31,12 @@ public interface ExternalSource { } @Override - public void deserialize(@NotNull BinaryReader reader, @NotNull ExternalSourceList object) throws IOException { + public void deserialize(@NotNull BinaryReader reader, @NotNull TypeFactory factory, @NotNull ExternalSourceList object) throws IOException { var count = reader.readInt(); var sources = new ArrayList(count); for (int i = 0; i < count; i++) { - ExternalSource source = RTTI.newInstance(ExternalSource.class); + ExternalSource source = factory.newInstance(ExternalSource.class); source.name(reader.readString(reader.readInt())); source.data(reader.readBytes(reader.readInt())); sources.add(source); diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/HavokClothResourceCallback.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/HavokClothResourceCallback.java index 3de5b748f..ac948af68 100644 --- a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/HavokClothResourceCallback.java +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/HavokClothResourceCallback.java @@ -1,6 +1,7 @@ package com.shade.decima.rtti.callbacks; import com.shade.decima.rtti.RTTI; +import com.shade.decima.rtti.TypeFactory; import com.shade.decima.rtti.serde.ExtraBinaryDataCallback; import com.shade.util.NotNull; import com.shade.util.io.BinaryReader; @@ -16,7 +17,7 @@ public interface HavokData { } @Override - public void deserialize(@NotNull BinaryReader reader, @NotNull HavokData object) throws IOException { + public void deserialize(@NotNull BinaryReader reader, @NotNull TypeFactory factory, @NotNull HavokData object) throws IOException { object.havokData(reader.readBytes(reader.readInt())); } } diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/IndexArrayResourceCallback.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/IndexArrayResourceCallback.java index d42e09976..043681c87 100644 --- a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/IndexArrayResourceCallback.java +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/IndexArrayResourceCallback.java @@ -1,6 +1,7 @@ package com.shade.decima.rtti.callbacks; import com.shade.decima.rtti.RTTI; +import com.shade.decima.rtti.TypeFactory; import com.shade.decima.rtti.serde.ExtraBinaryDataCallback; import com.shade.util.NotNull; import com.shade.util.io.BinaryReader; @@ -43,7 +44,7 @@ public interface Indices { } @Override - public void deserialize(@NotNull BinaryReader reader, @NotNull Indices object) throws IOException { + public void deserialize(@NotNull BinaryReader reader, @NotNull TypeFactory factory, @NotNull Indices object) throws IOException { object.count(reader.readInt()); if (object.count() > 0) { object.flags(reader.readInt()); diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/MorphemeAnimationResourceCallback.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/MorphemeAnimationResourceCallback.java index a592989de..2f2bf45cd 100644 --- a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/MorphemeAnimationResourceCallback.java +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/MorphemeAnimationResourceCallback.java @@ -1,6 +1,7 @@ package com.shade.decima.rtti.callbacks; import com.shade.decima.rtti.RTTI; +import com.shade.decima.rtti.TypeFactory; import com.shade.decima.rtti.serde.ExtraBinaryDataCallback; import com.shade.util.NotNull; import com.shade.util.io.BinaryReader; @@ -16,7 +17,7 @@ public interface MorphemeAnimationData { } @Override - public void deserialize(@NotNull BinaryReader reader, @NotNull MorphemeAnimationData object) throws IOException { + public void deserialize(@NotNull BinaryReader reader, @NotNull TypeFactory factory, @NotNull MorphemeAnimationData object) throws IOException { object.data(reader.readBytes(reader.readInt())); } } diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/NavMeshCallback.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/NavMeshCallback.java index 02646b3da..4a9b7b416 100644 --- a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/NavMeshCallback.java +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/NavMeshCallback.java @@ -1,6 +1,7 @@ package com.shade.decima.rtti.callbacks; import com.shade.decima.rtti.RTTI; +import com.shade.decima.rtti.TypeFactory; import com.shade.decima.rtti.serde.ExtraBinaryDataCallback; import com.shade.util.NotNull; import com.shade.util.io.BinaryReader; @@ -19,7 +20,7 @@ public interface NavMeshData { } @Override - public void deserialize(@NotNull BinaryReader reader, @NotNull NavMeshData object) throws IOException { + public void deserialize(@NotNull BinaryReader reader, @NotNull TypeFactory factory, @NotNull NavMeshData object) throws IOException { var start = reader.position(); var navMeshSetMagic = reader.readInt(); var navMeshSetVersion = reader.readInt(); diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/PhysicsCollisionResourceCallback.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/PhysicsCollisionResourceCallback.java index 82f1bb6ca..592c9b644 100644 --- a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/PhysicsCollisionResourceCallback.java +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/PhysicsCollisionResourceCallback.java @@ -1,6 +1,7 @@ package com.shade.decima.rtti.callbacks; import com.shade.decima.rtti.RTTI; +import com.shade.decima.rtti.TypeFactory; import com.shade.decima.rtti.serde.ExtraBinaryDataCallback; import com.shade.util.NotNull; import com.shade.util.io.BinaryReader; @@ -16,7 +17,7 @@ public interface HavokDataBlock { } @Override - public void deserialize(@NotNull BinaryReader reader, @NotNull HavokDataBlock object) throws IOException { + public void deserialize(@NotNull BinaryReader reader, @NotNull TypeFactory factory, @NotNull HavokDataBlock object) throws IOException { object.data(reader.readBytes(reader.readInt())); } } diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/ScaleformGFxMovieResourceCallback.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/ScaleformGFxMovieResourceCallback.java index bd39b5f02..cdb1849c1 100644 --- a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/ScaleformGFxMovieResourceCallback.java +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/ScaleformGFxMovieResourceCallback.java @@ -1,6 +1,7 @@ package com.shade.decima.rtti.callbacks; import com.shade.decima.rtti.RTTI; +import com.shade.decima.rtti.TypeFactory; import com.shade.decima.rtti.serde.ExtraBinaryDataCallback; import com.shade.util.NotNull; import com.shade.util.io.BinaryReader; @@ -16,7 +17,7 @@ public interface MovieData { } @Override - public void deserialize(@NotNull BinaryReader reader, @NotNull MovieData object) throws IOException { + public void deserialize(@NotNull BinaryReader reader, @NotNull TypeFactory factory, @NotNull MovieData object) throws IOException { object.data(reader.readBytes(Math.toIntExact(reader.readLong()))); } } diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/ShaderResourceCallback.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/ShaderResourceCallback.java index f9050418c..e7d4cebf1 100644 --- a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/ShaderResourceCallback.java +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/ShaderResourceCallback.java @@ -1,6 +1,7 @@ package com.shade.decima.rtti.callbacks; import com.shade.decima.rtti.RTTI; +import com.shade.decima.rtti.TypeFactory; import com.shade.decima.rtti.serde.ExtraBinaryDataCallback; import com.shade.util.NotNull; import com.shade.util.io.BinaryReader; @@ -16,7 +17,7 @@ public interface ShaderData { } @Override - public void deserialize(@NotNull BinaryReader reader, @NotNull ShaderData object) throws IOException { + public void deserialize(@NotNull BinaryReader reader, @NotNull TypeFactory factory, @NotNull ShaderData object) throws IOException { object.data(reader.readBytes(reader.readInt())); } } diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/TextureCallback.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/TextureCallback.java index 31634a0f5..e23ad1da5 100644 --- a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/TextureCallback.java +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/TextureCallback.java @@ -1,6 +1,7 @@ package com.shade.decima.rtti.callbacks; import com.shade.decima.rtti.RTTI; +import com.shade.decima.rtti.TypeFactory; import com.shade.decima.rtti.data.DataSource; import com.shade.decima.rtti.serde.ExtraBinaryDataCallback; import com.shade.util.NotNull; @@ -48,7 +49,7 @@ public interface TextureHeader { void unk0B(byte[] value); @NotNull - static TextureHeader read(@NotNull BinaryReader reader) throws IOException { + static TextureHeader read(@NotNull BinaryReader reader, @NotNull TypeFactory factory) throws IOException { var type = ETextureType.valueOf(reader.readByte()); var width = 1 << reader.readByte(); var height = 1 << reader.readByte(); @@ -57,7 +58,7 @@ static TextureHeader read(@NotNull BinaryReader reader) throws IOException { var pixelFormat = EPixelFormat.valueOf(reader.readByte()); var unk0B = reader.readBytes(20 + 16); - var object = RTTI.newInstance(TextureHeader.class); + var object = factory.newInstance(TextureHeader.class); object.type(type); object.width(width); object.height(height); @@ -102,7 +103,7 @@ public interface TextureData { void streamedData(DataSource value); @NotNull - static TextureData read(@NotNull BinaryReader reader) throws IOException { + static TextureData read(@NotNull BinaryReader reader, @NotNull TypeFactory factory) throws IOException { var remainingSize = reader.readInt(); var start = reader.position(); @@ -113,10 +114,10 @@ static TextureData read(@NotNull BinaryReader reader) throws IOException { var streamedMips = reader.readInt(); var embeddedData = reader.readBytes(embeddedSize); - var streamedData = streamedSize > 0 ? DataSource.read(reader) : null; + var streamedData = streamedSize > 0 ? DataSource.read(reader, factory) : null; assert reader.position() == start + remainingSize; - var object = RTTI.newInstance(TextureData.class); + var object = factory.newInstance(TextureData.class); object.totalSize(totalSize); object.embeddedSize(embeddedSize); object.streamedSize(streamedSize); @@ -141,8 +142,8 @@ public interface TextureInfo { } @Override - public void deserialize(@NotNull BinaryReader reader, @NotNull TextureInfo object) throws IOException { - object.header(TextureHeader.read(reader)); - object.data(TextureData.read(reader)); + public void deserialize(@NotNull BinaryReader reader, @NotNull TypeFactory factory, @NotNull TextureInfo object) throws IOException { + object.header(TextureHeader.read(reader, factory)); + object.data(TextureData.read(reader, factory)); } } diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/VertexArrayResourceCallback.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/VertexArrayResourceCallback.java index 370e414cb..9a89ac80c 100644 --- a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/VertexArrayResourceCallback.java +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/VertexArrayResourceCallback.java @@ -1,6 +1,7 @@ package com.shade.decima.rtti.callbacks; import com.shade.decima.rtti.RTTI; +import com.shade.decima.rtti.TypeFactory; import com.shade.decima.rtti.serde.ExtraBinaryDataCallback; import com.shade.util.NotNull; import com.shade.util.io.BinaryReader; @@ -33,13 +34,13 @@ public interface VertexStreamElement { void type(EVertexElement value); @NotNull - static VertexStreamElement read(@NotNull BinaryReader reader) throws IOException { + static VertexStreamElement read(@NotNull BinaryReader reader, @NotNull TypeFactory factory) throws IOException { var offset = reader.readByte(); var storageType = EVertexElementStorageType.valueOf(reader.readByte()); var slotsUsed = reader.readByte(); var type = EVertexElement.valueOf(reader.readByte()); - var element = RTTI.newInstance(VertexStreamElement.class); + var element = factory.newInstance(VertexStreamElement.class); element.offset(offset); element.storageType(storageType); element.slotsUsed(slotsUsed); @@ -76,14 +77,14 @@ public interface VertexStream { void data(byte[] value); @NotNull - static VertexStream read(@NotNull BinaryReader reader, int numVertices) throws IOException { + static VertexStream read(@NotNull BinaryReader reader, @NotNull TypeFactory factory, int numVertices) throws IOException { var flags = reader.readInt(); var stride = reader.readInt(); - var elements = reader.readObjects(reader.readInt(), VertexStreamElement::read); + var elements = reader.readObjects(reader.readInt(), r -> VertexStreamElement.read(r, factory)); var hash = reader.readBytes(16); var data = reader.readBytes(stride * numVertices); - var stream = RTTI.newInstance(VertexStream.class); + var stream = factory.newInstance(VertexStream.class); stream.flags(flags); stream.stride(stride); stream.elements(elements); @@ -107,10 +108,10 @@ public interface VertexArrayData { } @Override - public void deserialize(@NotNull BinaryReader reader, @NotNull VertexArrayData object) throws IOException { + public void deserialize(@NotNull BinaryReader reader, @NotNull TypeFactory factory, @NotNull VertexArrayData object) throws IOException { var numVertices = reader.readInt(); var numStreams = reader.readInt(); - var streams = reader.readObjects(numStreams, r -> VertexStream.read(r, numVertices)); + var streams = reader.readObjects(numStreams, r -> VertexStream.read(r, factory, numVertices)); object.count(numVertices); object.streams(streams); diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/WWiseSoundBankResourceCallback.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/WWiseSoundBankResourceCallback.java index de4d1bc98..6f852dd9f 100644 --- a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/WWiseSoundBankResourceCallback.java +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/callbacks/WWiseSoundBankResourceCallback.java @@ -1,6 +1,7 @@ package com.shade.decima.rtti.callbacks; import com.shade.decima.rtti.RTTI; +import com.shade.decima.rtti.TypeFactory; import com.shade.decima.rtti.serde.ExtraBinaryDataCallback; import com.shade.util.NotNull; import com.shade.util.io.BinaryReader; @@ -21,8 +22,8 @@ public interface SoundBankData { void data(byte[] value); @NotNull - static SoundBankData read(@NotNull BinaryReader reader) throws IOException { - var object = RTTI.newInstance(SoundBankData.class); + static SoundBankData read(@NotNull BinaryReader reader, @NotNull TypeFactory factory) throws IOException { + var object = factory.newInstance(SoundBankData.class); object.name(reader.readString(reader.readInt())); object.data(reader.readBytes(reader.readInt())); @@ -38,7 +39,7 @@ public interface SoundBankList { } @Override - public void deserialize(@NotNull BinaryReader reader, @NotNull SoundBankList object) throws IOException { - object.soundBanks(reader.readObjects(reader.readInt(), SoundBankData::read)); + public void deserialize(@NotNull BinaryReader reader, @NotNull TypeFactory factory, @NotNull SoundBankList object) throws IOException { + object.soundBanks(reader.readObjects(reader.readInt(), r -> SoundBankData.read(r, factory))); } } diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/data/DataSource.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/data/DataSource.java index e96845960..2de886e82 100644 --- a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/data/DataSource.java +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/data/DataSource.java @@ -1,6 +1,7 @@ package com.shade.decima.rtti.data; import com.shade.decima.rtti.RTTI; +import com.shade.decima.rtti.TypeFactory; import com.shade.util.NotNull; import com.shade.util.io.BinaryReader; @@ -23,8 +24,8 @@ public interface DataSource { void length(long value); @NotNull - static DataSource read(@NotNull BinaryReader reader) throws IOException { - DataSource source = RTTI.newInstance(DataSource.class); + static DataSource read(@NotNull BinaryReader reader, @NotNull TypeFactory factory) throws IOException { + DataSource source = factory.newInstance(DataSource.class); source.location(reader.readString(reader.readInt())); source.offset(reader.readLong()); source.length(reader.readLong()); diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/TypeFactoryTest.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/TypeFactoryTest.java new file mode 100644 index 000000000..5e33b2cd3 --- /dev/null +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/TypeFactoryTest.java @@ -0,0 +1,9 @@ +package com.shade.decima.rtti.test; + +public class TypeFactoryTest { + public static void main(String[] args) { + //var factory = new UntilDawnTypeFactory(); + //var info = factory.get(UntilDawn.Vec3.class); + //var object = factory.newInstance(info); + } +} diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/UntilDawnMain.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/UntilDawnMain.java index b9198e090..34e045c08 100644 --- a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/UntilDawnMain.java +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/UntilDawnMain.java @@ -1,6 +1,5 @@ package com.shade.decima.rtti.test; -import com.shade.util.io.BinaryReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,12 +14,8 @@ public class UntilDawnMain { private static final Logger log = LoggerFactory.getLogger(UntilDawnMain.class); public static void main(String[] args) throws IOException { - Path cache = Path.of("D:/PlayStation Games/Until Dawn/localcachepink"); - Path lump = cache.resolve("lumps/assets_description.application_concreteasset.core"); - - try (BinaryReader reader = CompressedBinaryReader.open(lump)) { - Files.write(Path.of("samples/until_dawn/assets_description.application_concreteasset.core"), reader.readBytes(Math.toIntExact(reader.size()))); - } + var factory = new UntilDawnTypeFactory(); + var cache = Path.of("D:/PlayStation Games/Until Dawn/localcachepink"); Files.walkFileTree(cache.resolve("lumps"), new SimpleFileVisitor<>() { @Override @@ -34,7 +29,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO return FileVisitResult.CONTINUE; } log.info("Reading {}", file); - try (UntilDawnReader reader = new UntilDawnReader(CompressedBinaryReader.open(file))) { + try (UntilDawnReader reader = new UntilDawnReader(CompressedBinaryReader.open(file), factory)) { var objects = reader.read(); log.info("Read {} objects", objects.size()); } diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/UntilDawnReader.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/UntilDawnReader.java index 3523a968b..80d39a077 100644 --- a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/UntilDawnReader.java +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/UntilDawnReader.java @@ -1,12 +1,11 @@ package com.shade.decima.rtti.test; -import com.shade.decima.rtti.RTTI; -import com.shade.decima.rtti.TypeName; +import com.shade.decima.rtti.TypeFactory; import com.shade.decima.rtti.UntilDawn; import com.shade.decima.rtti.data.Ref; import com.shade.decima.rtti.data.Value; +import com.shade.decima.rtti.runtime.*; import com.shade.decima.rtti.serde.ExtraBinaryDataHolder; -import com.shade.decima.rtti.serde.RTTIBinaryReader; import com.shade.util.NotImplementedException; import com.shade.util.NotNull; import com.shade.util.Nullable; @@ -15,29 +14,30 @@ import java.io.Closeable; import java.io.IOException; import java.lang.reflect.Array; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; -public class UntilDawnReader implements RTTIBinaryReader, Closeable { +public class UntilDawnReader implements Closeable { private final BinaryReader reader; + private final TypeFactory factory; private final Header header; - private final TypeInfo[] typeInfo; + private final RTTITypeInfo[] typeInfo; private final int[] objectTypes; private final ObjectHeader[] objectHeaders; private final List> pointers = new ArrayList<>(); - public UntilDawnReader(@NotNull BinaryReader reader) throws IOException { + public UntilDawnReader(@NotNull BinaryReader reader, @NotNull TypeFactory factory) throws IOException { this.reader = reader; this.header = Header.read(reader); + this.factory = factory; var typeInfoCount = reader.readInt(); - this.typeInfo = reader.readObjects(typeInfoCount, TypeInfo::read, TypeInfo[]::new); + this.typeInfo = reader.readObjects(typeInfoCount, RTTITypeInfo::read, RTTITypeInfo[]::new); var objectTypesCount = reader.readInt(); this.objectTypes = reader.readInts(objectTypesCount); @@ -47,7 +47,6 @@ public UntilDawnReader(@NotNull BinaryReader reader) throws IOException { } @NotNull - @Override public List read() throws IOException { List objects = new ArrayList<>(header.assetCount); @@ -56,7 +55,7 @@ public List read() throws IOException { var info = typeInfo[objectTypes[i]]; var header = objectHeaders[i]; - var object = readCompound(RTTI.getType(info.name, UntilDawn.class)); + var object = readCompound(factory.get(UntilDawnTypeId.of(info.name))); var end = reader.position(); if (header.size > 0 && end - start != header.size) { @@ -83,66 +82,71 @@ public void close() throws IOException { } @Nullable - @SuppressWarnings("unchecked") - private T readType(@NotNull Type type, @NotNull TypeName name) throws IOException { - if (type instanceof Class cls) { - if (cls.isPrimitive() || cls == String.class) { - return (T) readAtom(cls, name.fullName()); - } else if (cls.isArray()) { - return (T) readAtomContainer(cls, (TypeName.Parameterized) name); - } else if (cls.isEnum()) { - return (T) readEnum(cls); - } else { - return (T) readCompound(cls); - } - } else if (type instanceof ParameterizedType parameterized) { - Type argument = parameterized.getActualTypeArguments()[0]; - if (parameterized.getRawType() == List.class) { - return (T) readObjectContainer(argument, (TypeName.Parameterized) name); - } else if (parameterized.getRawType() == Ref.class) { - return (T) readPointer(); - } else if (parameterized.getRawType() == Value.class) { - return (T) readEnum((Class) parameterized.getActualTypeArguments()[0]); - } - } - throw new NotImplementedException(); + private Object readType(@NotNull TypeInfo info) throws IOException { + return switch (info) { + case AtomTypeInfo t -> readAtom(t); + case EnumTypeInfo t -> readEnum(t); + case ClassTypeInfo t -> readCompound(t); + case ContainerTypeInfo t -> readContainer(t); + case PointerTypeInfo t -> readPointer(t); + }; } @NotNull - private T readCompound(@NotNull Class cls) throws IOException { - T object = RTTI.newInstance(cls); - for (RTTI.AttributeInfo attr : RTTI.getAttrsSorted(cls)) { - if (!attr.serializable()) { - continue; - } - Object value = readType(attr.type(), attr.typeName()); - attr.set(object, value); + private Object readCompound(@NotNull ClassTypeInfo info) throws IOException { + Object object = info.newInstance(); + for (ClassAttrInfo attr : info.serializableAttrs()) { + attr.set(object, readType(attr.type().get())); } if (object instanceof ExtraBinaryDataHolder holder) { - holder.deserialize(reader); + holder.deserialize(reader, factory); } return object; } @NotNull - private Object readEnum(@NotNull Class cls) throws IOException { - var metadata = cls.getDeclaredAnnotation(RTTI.Serializable.class); - if (metadata == null) { - throw new IllegalArgumentException("Enum class '" + cls + "' is not annotated with " + RTTI.Serializable.class); + private Object readContainer(@NotNull ContainerTypeInfo info) throws IOException { + var itemInfo = info.itemType().get(); + var itemType = itemInfo.type(); + var count = reader.readInt(); + + // Fast path + if (itemType == byte.class) { + return reader.readBytes(count); + } else if (itemType == short.class) { + return reader.readShorts(count); + } else if (itemType == int.class) { + return reader.readInts(count); + } else if (itemType == long.class) { + return reader.readLongs(count); } - int value = switch (metadata.size()) { + // Slow path + var array = Array.newInstance((Class) itemType, count); + for (int i = 0; i < count; i++) { + Array.set(array, i, readType(itemInfo)); + } + + if (info.type() == List.class) { + return Arrays.asList((Object[]) array); + } else { + return array; + } + } + + @NotNull + private Object readEnum(@NotNull EnumTypeInfo info) throws IOException { + int value = switch (info.size()) { case Byte.BYTES -> reader.readByte(); case Short.BYTES -> reader.readShort(); case Integer.BYTES -> reader.readInt(); - default -> throw new IllegalArgumentException("Unexpected enum size: " + metadata.size()); - }; - - return switch (cls) { - case Class c when Value.OfEnum.class.isAssignableFrom(c) -> Value.valueOf(uncheckedCast(cls), value); - case Class c when Value.OfEnumSet.class.isAssignableFrom(c) -> Value.setOf(uncheckedCast(cls), value); - default -> throw new IllegalArgumentException("Unexpected type of enum: " + cls); + default -> throw new IllegalArgumentException("Unexpected enum size: " + info.size()); }; + if (info.flags()) { + return Value.setOf(uncheckedCast(info.type()), value); + } else { + return Value.valueOf(uncheckedCast(info.type()), value); + } } @SuppressWarnings("unchecked") @@ -152,8 +156,8 @@ private static T uncheckedCast(Object object) { @Nullable @SuppressWarnings("DuplicateBranchesInSwitch") - private Object readAtom(@NotNull Class cls, @NotNull String name) throws IOException { - return switch (name) { + private Object readAtom(@NotNull AtomTypeInfo info) throws IOException { + return switch (info.name().name()) { // Base types case "bool" -> reader.readBoolean(); case "wchar" -> (char) reader.readShort(); @@ -171,42 +175,42 @@ private Object readAtom(@NotNull Class cls, @NotNull String name) throws IOEx case "PhysicsCollisionFilterInfo" -> reader.readInt(); case "Filename" -> getString(reader); - default -> throw new IllegalArgumentException("Unknown atom type: " + name); + default -> throw new IllegalArgumentException("Unknown atom type: " + info.name()); }; } - @NotNull - private Object readAtomContainer(@NotNull Class cls, @NotNull TypeName.Parameterized name) throws IOException { - var component = cls.componentType(); - var length = reader.readInt(); - - if (component == byte.class) { - return reader.readBytes(length); - } else if (component == short.class) { - return reader.readShorts(length); - } else if (component == int.class) { - return reader.readInts(length); - } else { - var array = Array.newInstance(component, length); - for (int i = 0; i < length; i++) { - Array.set(array, i, readType(component, name.argument())); - } - return array; - } - } - - @NotNull - private List readObjectContainer(@NotNull Type type, @NotNull TypeName.Parameterized name) throws IOException { - var length = reader.readInt(); - var list = new ArrayList<>(length); - for (int i = 0; i < length; i++) { - list.add(readType(type, name.argument())); - } - return list; - } + //@NotNull + //private Object readAtomContainer(@NotNull Class cls, @NotNull TypeName.Parameterized name) throws IOException { + // var component = cls.componentType(); + // var length = reader.readInt(); + // + // if (component == byte.class) { + // return reader.readBytes(length); + // } else if (component == short.class) { + // return reader.readShorts(length); + // } else if (component == int.class) { + // return reader.readInts(length); + // } else { + // var array = Array.newInstance(component, length); + // for (int i = 0; i < length; i++) { + // Array.set(array, i, readType(component, name.argument())); + // } + // return array; + // } + //} + // + //@NotNull + //private List readObjectContainer(@NotNull Type type, @NotNull TypeName.Parameterized name) throws IOException { + // var length = reader.readInt(); + // var list = new ArrayList<>(length); + // for (int i = 0; i < length; i++) { + // list.add(readType(type, name.argument())); + // } + // return list; + //} @Nullable - private Ref readPointer() throws IOException { + private Ref readPointer(@NotNull PointerTypeInfo info) throws IOException { var kind = reader.readByte(); var pointer = switch (kind) { case 0 -> { @@ -279,12 +283,12 @@ static Header read(@NotNull BinaryReader reader) throws IOException { } } - private record TypeInfo(@NotNull String name, @NotNull byte[] hash) { + private record RTTITypeInfo(@NotNull String name, @NotNull byte[] hash) { @NotNull - static TypeInfo read(@NotNull BinaryReader reader) throws IOException { + static RTTITypeInfo read(@NotNull BinaryReader reader) throws IOException { var name = reader.readString(reader.readInt()); var hash = reader.readBytes(16); - return new TypeInfo(name, hash); + return new RTTITypeInfo(name, hash); } } diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/UntilDawnTypeFactory.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/UntilDawnTypeFactory.java new file mode 100644 index 000000000..8f5ab2b38 --- /dev/null +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/UntilDawnTypeFactory.java @@ -0,0 +1,83 @@ +package com.shade.decima.rtti.test; + +import com.shade.decima.rtti.AbstractTypeFactory; +import com.shade.decima.rtti.TypeId; +import com.shade.decima.rtti.UntilDawn; +import com.shade.decima.rtti.runtime.TypeInfo; +import com.shade.util.NotNull; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class UntilDawnTypeFactory extends AbstractTypeFactory { + public UntilDawnTypeFactory() { + super(UntilDawn.class); + } + + @NotNull + @Override + protected TypeId computeTypeId(@NotNull TypeInfo info) { + return new UntilDawnTypeId(info.name().fullName()); + } + + @Override + protected void sortSerializableAttrs(@NotNull List attrs) { + // This is a broken implementation of quicksort used by older versions of Decima + quicksort(attrs, Comparator.comparingInt(OrderedAttr::offset), 0, attrs.size() - 1, 0); + } + + @Override + protected void filterSerializableAttrs(@NotNull List attrs) { + // Remove save state attribute + attrs.removeIf(attr -> (attr.info().flags() & 2) != 0); + // Remove non-"serializable" attributes. They include holders for MsgReadBinary data + attrs.removeIf(attr -> !attr.serializable()); + } + + private static int quicksort(@NotNull List items, @NotNull Comparator comparator, int left, int right, int state) { + if (left < right) { + state = 0x19660D * state + 0x3C6EF35F; + + int pivot = (state >>> 8) % (right - left); + Collections.swap(items, left + pivot, left); + + int start = partition(items, comparator, left, right); + state = quicksort(items, comparator, left, start - 1, state); + state = quicksort(items, comparator, start + 1, right, state); + } + + return state; + } + + private static int partition(@NotNull List items, @NotNull Comparator comparator, int left, int right) { + var l = left - 1; + var r = right; + + while (true) { + do { + if (l >= r) { + break; + } + l++; + } while (comparator.compare(items.get(l), items.get(right)) < 0); + + do { + if (r <= l) { + break; + } + r--; + } while (comparator.compare(items.get(right), items.get(r)) < 0); + + if (l >= r) { + break; + } + + Collections.swap(items, l, r); + } + + Collections.swap(items, l, right); + + return l; + } +} diff --git a/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/UntilDawnTypeId.java b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/UntilDawnTypeId.java new file mode 100644 index 000000000..58d35da3a --- /dev/null +++ b/modules/decima-game-until-dawn/src/main/java/com/shade/decima/rtti/test/UntilDawnTypeId.java @@ -0,0 +1,11 @@ +package com.shade.decima.rtti.test; + +import com.shade.decima.rtti.TypeId; +import com.shade.util.NotNull; + +public record UntilDawnTypeId(@NotNull String typeName) implements TypeId { + @NotNull + public static TypeId of(@NotNull String typeName) { + return new UntilDawnTypeId(typeName); + } +} diff --git a/modules/decima-game-until-dawn/src/test/java/com/shade/decima/rtti/RTTITest.java b/modules/decima-game-until-dawn/src/test/java/com/shade/decima/rtti/RTTITest.java index 3bb202e83..2a14b685a 100644 --- a/modules/decima-game-until-dawn/src/test/java/com/shade/decima/rtti/RTTITest.java +++ b/modules/decima-game-until-dawn/src/test/java/com/shade/decima/rtti/RTTITest.java @@ -1,5 +1,8 @@ package com.shade.decima.rtti; +import com.shade.decima.rtti.runtime.ClassAttrInfo; +import com.shade.decima.rtti.test.UntilDawnTypeFactory; +import com.shade.decima.rtti.test.UntilDawnTypeId; import com.shade.util.NotNull; import nl.jqno.equalsverifier.EqualsVerifier; import nl.jqno.equalsverifier.Warning; @@ -14,11 +17,14 @@ import static org.junit.jupiter.api.Assertions.*; class RTTITest { + // TODO: Classes get redefined on every instantiation of the factory. Do something about that. + private static final TypeFactory factory = new UntilDawnTypeFactory(); + @ParameterizedTest @MethodSource("getTypes") void canFindAndConstructAndPassEqualityCheck(@NotNull String name) { - var iface = assertDoesNotThrow(() -> RTTI.getType(name, UntilDawn.class), "Can find type"); - var instance = assertDoesNotThrow(() -> RTTI.newInstance(iface), "Can construct type"); + var info = assertDoesNotThrow(() -> factory.get(UntilDawnTypeId.of(name)), "Can find type"); + var instance = assertDoesNotThrow(info::newInstance, "Can construct type"); var representation = instance.getClass(); EqualsVerifier.forClass(representation) @@ -28,13 +34,13 @@ void canFindAndConstructAndPassEqualityCheck(@NotNull String name) { @Test void canProperlySortAttributes() { - List attrs; + List attrs; // This type has a particularly weird sorting due to the fact that // all its attributes are properties and don't have an offset, so // the test ensures that they get sorted correctly using a dedicated // algorithm used internally by the engine. - attrs = RTTI.getAttrsSorted(RenderPass.class); + attrs = factory.get(RenderPass.class).serializableAttrs(); assertEquals(10, attrs.size()); assertEquals("HasAlphaTest", attrs.get(0).name()); assertEquals("Wireframe", attrs.get(1).name()); @@ -48,7 +54,7 @@ void canProperlySortAttributes() { assertEquals("DepthTestFunc", attrs.get(9).name()); // And for other types, the order should be by offset of their attributes - attrs = RTTI.getAttrsSorted(Vec3.class); + attrs = factory.get(Vec3.class).serializableAttrs(); assertEquals(3, attrs.size()); assertEquals("X", attrs.get(0).name()); assertEquals("Y", attrs.get(1).name()); @@ -57,7 +63,7 @@ void canProperlySortAttributes() { @Test void canGetAndSetAttributes() { - Vec3 vec3 = RTTI.newInstance(Vec3.class); + Vec3 vec3 = factory.newInstance(Vec3.class); assertEquals(0, vec3.x()); assertEquals(0, vec3.y()); assertEquals(0, vec3.z()); @@ -80,11 +86,13 @@ void canGetAndSetAttributes() { @Test void canToString() { - assertEquals("UntilDawn$Vec3$POD[X=0.0, Y=0.0, Z=0.0]", RTTI.newInstance(Vec3.class).toString()); + assertEquals("UntilDawn$Vec3$POD[X=0.0, Y=0.0, Z=0.0]", factory.newInstance(Vec3.class).toString()); } @NotNull private static Stream getTypes() { - return RTTI.getTypes(UntilDawn.class).stream().map(Class::getSimpleName); + return Stream.of(UntilDawn.class.getDeclaredClasses()) + .filter(Class::isInterface) + .map(Class::getSimpleName); } } diff --git a/modules/decima-rtti-generator/src/main/java/com/shade/decima/rtti/generator/TypeGenerator.java b/modules/decima-rtti-generator/src/main/java/com/shade/decima/rtti/generator/TypeGenerator.java index 22651aa29..880e78264 100644 --- a/modules/decima-rtti-generator/src/main/java/com/shade/decima/rtti/generator/TypeGenerator.java +++ b/modules/decima-rtti-generator/src/main/java/com/shade/decima/rtti/generator/TypeGenerator.java @@ -1,5 +1,6 @@ package com.shade.decima.rtti.generator; +import com.shade.decima.rtti.TypeFactory; import com.shade.decima.rtti.data.Value; import com.shade.decima.rtti.generator.data.*; import com.shade.decima.rtti.serde.ExtraBinaryDataHolder; @@ -82,8 +83,9 @@ private TypeSpec generateClass(@NotNull ClassTypeInfo info) { .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT) .addAnnotation(Override.class) .addParameter(BinaryReader.class, "reader") + .addParameter(TypeFactory.class, "factory") .addException(IOException.class) - .addCode("new $T().deserialize(reader, this);", callback.handlerType); + .addCode("new $T().deserialize(reader, factory, this);", callback.handlerType); builder.addMethod(deserialize.build()); builder.addSuperinterface(callback.holderType); diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/AbstractTypeFactory.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/AbstractTypeFactory.java new file mode 100644 index 000000000..e7cf134eb --- /dev/null +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/AbstractTypeFactory.java @@ -0,0 +1,353 @@ +package com.shade.decima.rtti; + +import com.shade.decima.rtti.data.Ref; +import com.shade.decima.rtti.data.Value; +import com.shade.decima.rtti.runtime.*; +import com.shade.util.NotNull; +import com.shade.util.Nullable; + +import java.lang.invoke.MethodHandles; +import java.lang.reflect.*; +import java.math.BigInteger; +import java.util.*; + +public abstract class AbstractTypeFactory implements TypeFactory { + private final Map pending = new HashMap<>(); + private final Map cache = new HashMap<>(); + private final Map ids = new HashMap<>(); + + private static class IsolatedClassLoader extends ClassLoader { + private final MethodHandles.Lookup lookup = MethodHandles.lookup(); + } + + private final IsolatedClassLoader classLoader = new IsolatedClassLoader(); + + protected AbstractTypeFactory(@NotNull Class namespace) { + try { + for (Class cls : namespace.getDeclaredClasses()) { + if (cls.isInterface()) { + var name = TypeName.of(cls.getSimpleName()); + var info = lookup(name, cls).get(); + var id = computeTypeId(info); + ids.put(id, info); + } + } + if (!pending.isEmpty()) { + throw new IllegalStateException("Some types left unresolved: " + pending); + } + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Unable to initialize factory", e); + } + } + + @NotNull + private static AtomTypeInfo createAtomInfo(@NotNull TypeName.Simple name, @NotNull Class type) { + return new AtomTypeInfo(name, type); + } + + @NotNull + @Override + public ClassTypeInfo get(@NotNull TypeId id) { + var info = ids.get(id); + if (info == null) { + throw new IllegalArgumentException("Unknown type: " + id); + } + return (ClassTypeInfo) info; + } + + @NotNull + @Override + public ClassTypeInfo get(@NotNull Class cls) { + TypeInfo info = cache.get(TypeName.of(cls.getSimpleName())); + if (info == null) { + throw new IllegalArgumentException("Unknown type: " + cls); + } + if (info.type() instanceof Class c && cls.isAssignableFrom(c)) { + return (ClassTypeInfo) info; + } + throw new IllegalArgumentException("Invalid RTTI compound: " + cls); + } + + @NotNull + protected TypeInfoRef lookup( + @NotNull TypeName name, + @NotNull Type type + ) throws ReflectiveOperationException { + if (pending.containsKey(name)) { + return pending.get(name); + } + + var info = cache.get(name); + if (info != null) { + return new ResolvedRef(info); + } + + pending.put(name, new FutureRef(name)); + + var result = switch (type) { + // @formatter:off + case Class cls when cls == String.class || cls == BigInteger.class -> + createAtomInfo((TypeName.Simple) name, cls); + case Class cls when cls.isPrimitive() -> + createAtomInfo((TypeName.Simple) name, cls); + case Class cls when cls.isInterface() -> + createClassInfo((TypeName.Simple) name, cls); + case Class cls when cls.isEnum() -> + createEnumInfo((TypeName.Simple) name, cls); + case Class cls when cls.isArray() -> + createContainerInfo((TypeName.Parameterized) name, cls, cls.componentType()); + case ParameterizedType p when p.getRawType() == List.class -> + createContainerInfo((TypeName.Parameterized) name, (Class) p.getRawType(), p.getActualTypeArguments()[0]); + case ParameterizedType p when p.getRawType() == Ref.class -> + createPointerInfo((TypeName.Parameterized) name, (Class) p.getRawType(), p.getActualTypeArguments()[0]); + case ParameterizedType p when p.getRawType() == Value.class -> + createEnumInfo((TypeName.Simple) name, (Class) p.getActualTypeArguments()[0]); + // @formatter:on + default -> throw new IllegalArgumentException("Unexpected type: " + name); + }; + + return resolve(result); + } + + @NotNull + private ContainerTypeInfo createContainerInfo( + @NotNull TypeName.Parameterized name, + @NotNull Class rawType, + @NotNull Type itemType + ) throws ReflectiveOperationException { + return new ContainerTypeInfo(name, rawType, lookup(name.argument(), itemType)); + } + + @NotNull + private PointerTypeInfo createPointerInfo( + @NotNull TypeName.Parameterized name, + @NotNull Class rawType, + @NotNull Type itemType + ) throws ReflectiveOperationException { + return new PointerTypeInfo(name, rawType, lookup(name.argument(), itemType)); + } + + @SuppressWarnings("unchecked") + @NotNull + private EnumTypeInfo createEnumInfo(@NotNull TypeName.Simple name, @NotNull Class type) { + RTTI.Serializable serializable = type.getDeclaredAnnotation(RTTI.Serializable.class); + if (serializable == null) { + throw new IllegalArgumentException("Enum class '" + type + "' is not annotated with " + RTTI.Serializable.class); + } + return new EnumTypeInfo( + name, + (Class>) type, + serializable.size(), + Value.OfEnumSet.class.isAssignableFrom(type) + ); + } + + @NotNull + private TypeInfoRef resolve(@NotNull TypeInfo info) { + FutureRef ref = pending.remove(info.name()); + if (ref == null) { + throw new IllegalStateException("Type was not present in the queue: " + info.name()); + } + if (cache.put(info.name(), info) != null) { + throw new IllegalStateException("Type was already resolved: " + info.name()); + } + ref.resolved = info; + return ref; + } + + @NotNull + private ClassTypeInfo createClassInfo( + @NotNull TypeName.Simple name, + @NotNull Class cls + ) throws ReflectiveOperationException { + var lookup = MethodHandles.privateLookupIn(cls, classLoader.lookup); + var holder = RuntimeTypeGenerator.generate(cls, lookup); + return new ClassTypeInfo( + name, + holder.lookupClass(), + collectBases(cls), + collectDeclaredAttrs(cls, holder), + collectSerializableAttrs(cls, holder) + ); + } + + @NotNull + private List collectBases(@NotNull Class cls) throws ReflectiveOperationException { + List bases = new ArrayList<>(); + for (AnnotatedType type : cls.getAnnotatedInterfaces()) { + RTTI.Base base = type.getDeclaredAnnotation(RTTI.Base.class); + if (base != null) { + var baseType = (Class) type.getType(); + var baseTypeName = TypeName.of(baseType.getSimpleName()); + var baseTypeInfo = lookup(baseTypeName, baseType); + bases.add(new ClassBaseInfo(baseTypeInfo, base.offset())); + } + } + return List.copyOf(bases); + } + + @NotNull + private List collectSerializableAttrs( + @NotNull Class cls, + @NotNull MethodHandles.Lookup lookup + ) throws ReflectiveOperationException { + //if (cls.getSimpleName().equals("")) + List attrs = new ArrayList<>(); + collectSerializableAttrs(cls, lookup, attrs, 0); + filterSerializableAttrs(attrs); + sortSerializableAttrs(attrs); + return attrs.stream() + .map(OrderedAttr::info) + .toList(); + } + + private void collectSerializableAttrs( + @NotNull Class cls, + @NotNull MethodHandles.Lookup lookup, + @NotNull List output, + int offset + ) throws ReflectiveOperationException { + for (AnnotatedType type : cls.getAnnotatedInterfaces()) { + RTTI.Base base = type.getDeclaredAnnotation(RTTI.Base.class); + if (base != null) { + collectSerializableAttrs((Class) type.getType(), lookup, output, offset + base.offset()); + } else { + collectSerializableAttrs((Class) type.getType(), lookup, output, offset); + } + } + collectDeclaredAttrs(cls, lookup, output, offset); + } + + @NotNull + private List collectDeclaredAttrs( + @NotNull Class cls, + @NotNull MethodHandles.Lookup lookup + ) throws ReflectiveOperationException { + List attrs = new ArrayList<>(); + collectDeclaredAttrs(cls, lookup, attrs, 0); + return attrs.stream() + .map(OrderedAttr::info) + .toList(); + } + + private void collectDeclaredAttrs( + @NotNull Class cls, + @NotNull MethodHandles.Lookup lookup, + @NotNull List output, + int offset + ) throws ReflectiveOperationException { + var serializable = cls.isAnnotationPresent(RTTI.Serializable.class); + var start = output.size(); + for (Method method : cls.getDeclaredMethods()) { + if (!Modifier.isAbstract(method.getModifiers())) { + // We'll look for the overloaded version of it + continue; + } + RTTI.Category category = method.getDeclaredAnnotation(RTTI.Category.class); + if (category != null) { + collectCategoryAttrs(method.getReturnType(), category.name(), lookup, output, offset, serializable); + } else { + collectAttr(method, null, offset, serializable, lookup, output); + } + } + output + .subList(start, output.size()) + .sort(Comparator.comparingInt(OrderedAttr::position)); + } + + private void collectCategoryAttrs( + @NotNull Class cls, + @NotNull String name, + @NotNull MethodHandles.Lookup lookup, + @NotNull List output, + int offset, + boolean serializable + ) throws ReflectiveOperationException { + for (Method method : cls.getDeclaredMethods()) { + collectAttr(method, name, offset, serializable, lookup, output); + } + } + + private void collectAttr( + @NotNull Method method, + @Nullable String category, + int offset, + boolean serializable, + @NotNull MethodHandles.Lookup lookup, + @NotNull List output + ) throws ReflectiveOperationException { + RTTI.Attr attr = method.getDeclaredAnnotation(RTTI.Attr.class); + if (attr == null && method.getReturnType() != void.class) { + throw new IllegalArgumentException("Unexpected method: " + method); + } + if (attr != null) { + String fieldName; + if (category != null) { + fieldName = category + '$' + attr.name(); + } else { + fieldName = attr.name(); + } + ClassAttrInfo info = new ClassAttrInfo( + attr.name(), + category, + lookup(TypeName.parse(attr.type()), method.getGenericReturnType()), + lookup.findVarHandle(lookup.lookupClass(), fieldName, method.getReturnType()), + attr.offset(), + attr.flags() + ); + output.add(new OrderedAttr(info, attr.position(), attr.offset() + offset, serializable)); + } + } + + @NotNull + protected abstract TypeId computeTypeId(@NotNull TypeInfo info); + + protected abstract void sortSerializableAttrs(@NotNull List attrs); + + protected abstract void filterSerializableAttrs(@NotNull List attrs); + + protected record OrderedAttr(@NotNull ClassAttrInfo info, int position, int offset, boolean serializable) { + } + + protected static class FutureRef implements TypeInfoRef { + private final TypeName name; + private TypeInfo resolved; + + public FutureRef(@NotNull TypeName name) { + this.name = name; + } + + @NotNull + @Override + public TypeName name() { + return name; + } + + @NotNull + @Override + public TypeInfo get() { + if (resolved == null) { + throw new IllegalStateException("Type '" + name + "' is not resolved"); + } + return resolved; + } + + @Override + public String toString() { + return resolved != null ? resolved.toString() : ""; + } + } + + protected record ResolvedRef(@NotNull TypeInfo get) implements TypeInfoRef { + @NotNull + @Override + public TypeName name() { + return get.name(); + } + + @Override + public String toString() { + return get.toString(); + } + } +} diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/RTTI.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/RTTI.java index db6922d99..66b577698 100644 --- a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/RTTI.java +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/RTTI.java @@ -1,27 +1,9 @@ package com.shade.decima.rtti; -import com.shade.decima.rtti.data.Ref; -import com.shade.decima.rtti.data.Value; -import com.shade.util.NotNull; -import com.shade.util.Nullable; -import org.objectweb.asm.Handle; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.invoke.VarHandle; -import java.lang.reflect.AnnotatedType; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -import static org.objectweb.asm.Opcodes.H_INVOKESTATIC; public class RTTI { @Retention(RetentionPolicy.RUNTIME) @@ -64,459 +46,6 @@ public class RTTI { int offset(); } - public interface Compound { - Class getType(); - } - - private static final Handle BOOTSTRAP_HANDLE = new Handle( - H_INVOKESTATIC, - "java/lang/runtime/ObjectMethods", - "bootstrap", - "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;", - false - ); - - private static final Map, Map>> namespaceCache = new ConcurrentHashMap<>(); - private static final Map, RepresentationInfo> representationCache = new ConcurrentHashMap<>(); - private static final Map, List> attributeCache = new ConcurrentHashMap<>(); - private static final Map, List> categoryCache = new ConcurrentHashMap<>(); - private RTTI() { } - - @NotNull - public static String getTypeName(@NotNull Object object) { - return getType(object).getSimpleName(); - } - - @NotNull - public static Class getType(@NotNull Object object) { - if (object instanceof Compound compound) { - return compound.getType(); - } else { - return object.getClass(); - } - } - - @NotNull - public static Class getType(@NotNull Class cls) { - if (!isCompound(cls)) { - throw new IllegalStateException("Can't get type of a non-compound type"); - } - if (Compound.class.isAssignableFrom(cls)) { - // Runtime representation - return cls.getInterfaces()[0]; - } else { - return cls; - } - } - - public static boolean isCompound(@NotNull Class cls) { - return Compound.class.isAssignableFrom(cls) - || cls.isInterface() && !isContainer(cls) && !isPointer(cls); - } - - public static boolean isEnum(@NotNull Class cls) { - return cls.isEnum() && Value.class.isAssignableFrom(cls); - } - - public static boolean isContainer(@NotNull Class cls) { - return cls.isArray() || List.class.isAssignableFrom(cls); - } - - public static boolean isPointer(@NotNull Class cls) { - return Ref.class.isAssignableFrom(cls); - } - - public static boolean isAtom(@NotNull Class cls) { - return cls.isPrimitive() || String.class.isAssignableFrom(cls) || Number.class.isAssignableFrom(cls); - } - - @NotNull - public static Class getType(@NotNull String name, @NotNull Class namespace) { - Class cls = namespaceCache - .computeIfAbsent(namespace, RTTI::getNamespaceTypes) - .get(name); - if (cls == null) { - throw new IllegalStateException("Type not found: " + name); - } - return cls; - } - - @NotNull - public static Collection> getTypes(@NotNull Class namespace) { - return Collections.unmodifiableCollection(namespaceCache.computeIfAbsent(namespace, RTTI::getNamespaceTypes).values()); - } - - @NotNull - public static List getAttrsSorted(@NotNull Class cls) { - if (!isCompound(cls)) { - throw new IllegalStateException("Can't get attributes of a non-compound type"); - } - return attributeCache.computeIfAbsent(getType(cls), RTTI::getAttrsSorted0); - } - - @NotNull - public static List getCategories(@NotNull Class cls) { - if (!isCompound(cls)) { - throw new IllegalStateException("Can't get categories of a non-compound type"); - } - return categoryCache.computeIfAbsent(getType(cls), RTTI::getCategories0); - } - - @NotNull - public static T newInstance(@NotNull Class cls) { - RepresentationInfo type = getRepresentationInfo(cls); - try { - return cls.cast(type.constructor.invoke()); - } catch (Throwable e) { - throw new IllegalStateException("Failed to create an instance of " + cls, e); - } - } - - @NotNull - private static RepresentationInfo getRepresentationInfo(@NotNull Class cls) { - if (!isCompound(cls)) { - throw new IllegalStateException("Can't get representation of a non-compound type"); - } - return representationCache.computeIfAbsent(getType(cls), RTTI::getRepresentationInfo0); - } - - @NotNull - private static List getCategories0(@NotNull Class cls) { - List categories = new ArrayList<>(); - for (Method method : cls.getMethods()) { - if (!Modifier.isAbstract(method.getModifiers())) { - // Skips overridden methods (e.g. categories) - continue; - } - Category category = method.getDeclaredAnnotation(Category.class); - if (category != null) { - categories.add(new CategoryInfo(category.name(), method.getReturnType(), method)); - } - } - categories.sort(Comparator.comparing(CategoryInfo::name)); - return categories; - } - - @NotNull - private static Map> getNamespaceTypes(@NotNull Class namespace) { - Map> map = new HashMap<>(); - for (Class cls : namespace.getDeclaredClasses()) { - if (!cls.isInterface()) { - continue; - } - map.put(cls.getSimpleName(), cls); - } - return map; - } - - @NotNull - private static RepresentationInfo getRepresentationInfo0(@NotNull Class iface) { - try { - var lookup = MethodHandles.privateLookupIn(iface, MethodHandles.lookup()); - var clazz = RuntimeTypeGenerator.generate(iface, lookup); - var constructor = lookup.findConstructor(clazz, MethodType.methodType(void.class)); - - return new RepresentationInfo(clazz, constructor); - } catch (Exception e) { - throw new IllegalStateException("Failed to create representation type", e); - } - } - - @NotNull - private static List getAttrsSorted0(@NotNull Class cls) { - List attrs = new ArrayList<>(); - collectAttrs(cls, attrs, 0); - filterAttrs(attrs); - sortAttrs(attrs); - return Collections.unmodifiableList(attrs); - } - - private static void filterAttrs(@NotNull List attrs) { - attrs.removeIf(attr -> (attr.flags & 2) != 0); // remove save-state only - } - - private static void collectAttrs( - @NotNull Class cls, - @NotNull List attrs, - int offset - ) { - for (BaseInfo base : collectBases(cls)) { - collectAttrs(base.cls, attrs, base.offset + offset); - } - collectAttrs(cls, attrs, offset, cls.isAnnotationPresent(Serializable.class)); - } - - private static void collectAttrs( - @NotNull Class cls, - @NotNull List attrs, - int offset, - boolean serializable - ) { - List sorted = new ArrayList<>(); - for (Method method : cls.getDeclaredMethods()) { - if (!Modifier.isAbstract(method.getModifiers())) { - // Skips overridden methods (e.g. categories) - continue; - } - Category category = method.getDeclaredAnnotation(Category.class); - if (category != null) { - collectCategoryAttrs(method.getReturnType(), category.name(), method, cls, sorted, offset, serializable); - } else { - collectAttr(method, null, cls, sorted, offset, serializable); - } - } - sorted.sort(Comparator.comparingInt(info -> info.position)); - attrs.addAll(sorted); - } - - private static void collectCategoryAttrs( - @NotNull Class cls, - @NotNull String name, - @NotNull Method getter, - @NotNull Class parent, - @NotNull List attrs, - int offset, - boolean serializable - ) { - CategoryInfo category = new CategoryInfo(name, cls, getter); - for (Method method : cls.getDeclaredMethods()) { - collectAttr(method, category, parent, attrs, offset, serializable); - } - } - - private static void collectAttr( - @NotNull Method method, - @Nullable CategoryInfo category, - @NotNull Class parent, - @NotNull List attrs, - int offset, - boolean serializable - ) { - Attr attr = method.getDeclaredAnnotation(Attr.class); - if (attr != null) { - Method setter; - try { - setter = method.getDeclaringClass().getDeclaredMethod(method.getName(), method.getReturnType()); - } catch (NoSuchMethodException e) { - throw new IllegalStateException("Setter not found: " + method, e); - } - attrs.add(new AttributeInfo( - attr.name(), - category, - attr.type(), - method.getGenericReturnType(), - parent, - attr.position(), - attr.offset() + offset, - attr.flags(), - serializable - )); - } else if (method.getReturnType() != void.class) { - throw new IllegalStateException("Unexpected method: " + method); - } - } - - @NotNull - private static List collectBases(@NotNull Class cls) { - List bases = new ArrayList<>(); - for (AnnotatedType type : cls.getAnnotatedInterfaces()) { - var base = type.getDeclaredAnnotation(Base.class); - var offset = base != null ? base.offset() : 0; - bases.add(new BaseInfo((Class) type.getType(), offset)); - } - bases.sort(Comparator.comparingInt(BaseInfo::offset)); - return bases; - } - - private static void sortAttrs(@NotNull List attrs) { - quicksort(attrs, Comparator.comparingInt(attr -> attr.offset)); - } - - private static void quicksort(@NotNull List items, @NotNull Comparator comparator) { - quicksort(items, 0, items.size() - 1, comparator, 0); - } - - private static int quicksort( - @NotNull List items, - int left, - int right, - @NotNull Comparator comparator, - int state - ) { - if (left < right) { - state = 0x19660D * state + 0x3C6EF35F; - - int pivot = (state >>> 8) % (right - left); - Collections.swap(items, left + pivot, left); - - int start = partition(items, left, right, comparator); - state = quicksort(items, left, start - 1, comparator, state); - state = quicksort(items, start + 1, right, comparator, state); - } - - return state; - } - - private static int partition(@NotNull List items, int left, int right, @NotNull Comparator comparator) { - var l = left - 1; - var r = right; - - while (true) { - do { - if (l >= r) { - break; - } - l++; - } while (comparator.compare(items.get(l), items.get(right)) < 0); - - do { - if (r <= l) { - break; - } - r--; - } while (comparator.compare(items.get(right), items.get(r)) < 0); - - if (l >= r) { - break; - } - - Collections.swap(items, l, r); - } - - Collections.swap(items, l, right); - - return l; - } - - public record CategoryInfo( - @NotNull String name, - @NotNull Class type, - @NotNull Method getter - ) { - } - - public static final class AttributeInfo { - private final String name; - private final CategoryInfo category; - private final TypeName typeName; - private final java.lang.reflect.Type type; - private final Class parent; - - private final int position; - private final int offset; - private final int flags; - private final boolean serializable; - - private VarHandle handle; - - private AttributeInfo( - @NotNull String name, - @Nullable CategoryInfo category, - @NotNull String typeName, - @NotNull java.lang.reflect.Type type, - @NotNull Class parent, - int position, - int offset, - int flags, - boolean serializable - ) { - this.name = name; - this.category = category; - this.typeName = TypeName.parse(typeName); - this.type = type; - this.parent = parent; - this.position = position; - this.offset = offset; - this.flags = flags; - this.serializable = serializable; - } - - @NotNull - public String name() { - return name; - } - - @NotNull - public TypeName typeName() { - return typeName; - } - - @NotNull - public java.lang.reflect.Type type() { - return type; - } - - public int flags() { - return flags; - } - - public int offset() { - return offset; - } - - public int position() { - return position; - } - - public boolean serializable() { - return serializable; - } - - public Object get(@NotNull Object instance) { - try { - return fieldHandle(instance).get(instance); - } catch (Exception e) { - throw new IllegalStateException("Failed to get attribute: " + name, e); - } - } - - public void set(@NotNull Object instance, @Nullable Object value) { - try { - fieldHandle(instance).set(instance, value); - } catch (Exception e) { - throw new IllegalStateException("Failed to set attribute: " + name, e); - } - } - - @Override - public String toString() { - return typeName + " " + parent.getSimpleName() + "." + name; - } - - @NotNull - private String fieldName() { - if (category != null) { - return category.name + '$' + name; - } else { - return name; - } - } - - @NotNull - private VarHandle fieldHandle(@NotNull Object object) { - if (handle == null) { - if (!parent.isInstance(object)) { - throw new IllegalArgumentException("Object is not an instance of " + parent); - } - try { - var cls = object.getClass(); - var lookup = MethodHandles.privateLookupIn(cls, MethodHandles.lookup()); - var rawType = (Class) (type instanceof ParameterizedType p ? p.getRawType() : type); - handle = lookup.findVarHandle(cls, fieldName(), rawType); - } catch (ReflectiveOperationException e) { - throw new IllegalStateException("Failed to get field handle", e); - } - } - return handle; - } - } - - private record BaseInfo(@NotNull Class cls, int offset) {} - - private record RepresentationInfo( - @NotNull Class cls, - @NotNull MethodHandle constructor - ) {} } diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/RuntimeTypeGenerator.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/RuntimeTypeGenerator.java index 9aadf07b0..ea2456c8a 100644 --- a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/RuntimeTypeGenerator.java +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/RuntimeTypeGenerator.java @@ -36,13 +36,16 @@ final class RuntimeTypeGenerator { false ); + private RuntimeTypeGenerator() { + } + /** * Creates a concrete class that implements the given {@code cls} interface. *

* The returned class can be instantiated by calling the default public constructor. */ @NotNull - public static Class generate( + public static MethodHandles.Lookup generate( @NotNull Class cls, @NotNull MethodHandles.Lookup lookup ) throws ReflectiveOperationException { @@ -53,7 +56,7 @@ public static Class generate( } @NotNull - private static Class generateClass( + private static MethodHandles.Lookup generateClass( @NotNull Class cls, @NotNull MethodHandles.Lookup lookup ) throws ReflectiveOperationException { @@ -81,7 +84,7 @@ private static Class generateClass( for (AttrInfo attr : attrs) { generateAttrField(writer, attr); - if (attr.category == null) { + if (attr.category() == null) { generateAttrGetter(writer, name, attr); generateAttrSetter(writer, name, attr); } @@ -103,7 +106,7 @@ private static Class generateClass( writer.visitEnd(); - return lookup.defineClass(writer.toByteArray()); + return MethodHandles.privateLookupIn(lookup.defineClass(writer.toByteArray()), lookup); } private static void generateCategoryField( @@ -113,16 +116,16 @@ private static void generateCategoryField( @NotNull Type categoryName, @NotNull CategoryInfo category ) { - var type = Type.getReturnType(category.getter); + var type = Type.getReturnType(category.getter()); constructor.visitVarInsn(ALOAD, 0); constructor.visitTypeInsn(NEW, categoryName.getInternalName()); constructor.visitInsn(DUP); constructor.visitVarInsn(ALOAD, 0); constructor.visitMethodInsn(INVOKESPECIAL, categoryName.getInternalName(), ConstantDescs.INIT_NAME, Type.getMethodDescriptor(Type.VOID_TYPE, className), false); - constructor.visitFieldInsn(PUTFIELD, className.getInternalName(), category.name, type.getDescriptor()); + constructor.visitFieldInsn(PUTFIELD, className.getInternalName(), category.name(), type.getDescriptor()); - writer.visitField(ACC_FINAL | ACC_SYNTHETIC, category.name, type.getDescriptor(), null, null).visitEnd(); + writer.visitField(ACC_FINAL | ACC_SYNTHETIC, category.name(), type.getDescriptor(), null, null).visitEnd(); writer.visitNestMember(categoryName.getInternalName()); writer.visitInnerClass(categoryName.getInternalName(), null, null, 0); } @@ -132,11 +135,11 @@ private static void generateCategoryGetter( @NotNull Type className, @NotNull CategoryInfo category ) { - var type = Type.getReturnType(category.getter); - var getter = writer.visitMethod(ACC_PUBLIC, category.getter.getName(), Type.getMethodDescriptor(category.getter), null, null); + var type = Type.getReturnType(category.getter()); + var getter = writer.visitMethod(ACC_PUBLIC, category.getter().getName(), Type.getMethodDescriptor(category.getter()), null, null); getter.visitCode(); getter.visitVarInsn(ALOAD, 0); - getter.visitFieldInsn(GETFIELD, className.getInternalName(), category.name, type.getDescriptor()); + getter.visitFieldInsn(GETFIELD, className.getInternalName(), category.name(), type.getDescriptor()); getter.visitInsn(ARETURN); getter.visitMaxs(0, 0); getter.visitEnd(); @@ -152,7 +155,7 @@ private static void prepareBootstrapArguments( List handles = new ArrayList<>(); for (AttrInfo attr : attrs) { - if (attr.getter.getReturnType().isArray()) { + if (attr.getter().getReturnType().isArray()) { arrays.add(attr); continue; } @@ -161,7 +164,7 @@ private static void prepareBootstrapArguments( H_GETFIELD, className.getInternalName(), attr.fieldName(), - Type.getReturnType(attr.getter).getDescriptor(), + Type.getReturnType(attr.getter()).getDescriptor(), false )); } @@ -172,7 +175,7 @@ private static void prepareBootstrapArguments( } private static void generateAttrField(@NotNull ClassWriter writer, @NotNull AttrInfo attr) { - var type = Type.getReturnType(attr.getter); + var type = Type.getReturnType(attr.getter()); var field = writer.visitField(ACC_PRIVATE, attr.fieldName(), type.getDescriptor(), null, null); field.visitEnd(); } @@ -182,8 +185,8 @@ private static void generateAttrSetter( @NotNull Type className, @NotNull AttrInfo attr ) { - var type = Type.getReturnType(attr.getter); - var method = writer.visitMethod(ACC_PUBLIC, attr.setter.getName(), Type.getMethodDescriptor(attr.setter), null, null); + var type = Type.getReturnType(attr.getter()); + var method = writer.visitMethod(ACC_PUBLIC, attr.setter().getName(), Type.getMethodDescriptor(attr.setter()), null, null); method.visitCode(); method.visitVarInsn(ALOAD, 0); method.visitVarInsn(type.getOpcode(ILOAD), 1); @@ -198,8 +201,8 @@ private static void generateAttrGetter( @NotNull Type className, @NotNull AttrInfo attr ) { - var type = Type.getReturnType(attr.getter); - var method = writer.visitMethod(ACC_PUBLIC, attr.getter.getName(), Type.getMethodDescriptor(attr.getter), null, null); + var type = Type.getReturnType(attr.getter()); + var method = writer.visitMethod(ACC_PUBLIC, attr.getter().getName(), Type.getMethodDescriptor(attr.getter()), null, null); method.visitCode(); method.visitVarInsn(ALOAD, 0); method.visitFieldInsn(GETFIELD, className.getInternalName(), attr.name(), type.getDescriptor()); @@ -215,7 +218,7 @@ private static void generateCategoryClass( @NotNull MethodHandles.Lookup lookup ) throws ReflectiveOperationException { var writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - writer.visit(V11, ACC_SUPER, className.getInternalName(), null, Type.getInternalName(Object.class), new String[]{Type.getReturnType(category.getter).getInternalName()}); + writer.visit(V11, ACC_SUPER, className.getInternalName(), null, Type.getInternalName(Object.class), new String[]{Type.getReturnType(category.getter()).getInternalName()}); writer.visitNestHost(hostClassName.getInternalName()); writer.visitOuterClass(hostClassName.getInternalName(), null, null); writer.visitInnerClass(className.getInternalName(), null, null, 0); @@ -380,6 +383,23 @@ private static void generateToString( method.visitEnd(); } + @NotNull + private static List collectCategories(@NotNull Class cls) { + List categories = new ArrayList<>(); + for (Method method : cls.getMethods()) { + if (!Modifier.isAbstract(method.getModifiers())) { + // We'll look for the overloaded version of it + continue; + } + RTTI.Category category = method.getDeclaredAnnotation(RTTI.Category.class); + if (category != null) { + categories.add(new CategoryInfo(category.name(), method.getReturnType(), method)); + } + } + categories.sort(Comparator.comparing(CategoryInfo::name)); + return categories; + } + @NotNull private static List collectAttrs(@NotNull Class cls) throws ReflectiveOperationException { List attrs = new ArrayList<>(); @@ -446,23 +466,6 @@ private static void collectAttr( } } - @NotNull - private static List collectCategories(@NotNull Class cls) { - List categories = new ArrayList<>(); - for (Method method : cls.getMethods()) { - if (!Modifier.isAbstract(method.getModifiers())) { - // We'll look for the overloaded version of it - continue; - } - RTTI.Category category = method.getDeclaredAnnotation(RTTI.Category.class); - if (category != null) { - categories.add(new CategoryInfo(category.name(), method.getReturnType(), method)); - } - } - categories.sort(Comparator.comparing(CategoryInfo::name)); - return categories; - } - private record CategoryInfo( @NotNull String name, @NotNull Class type, @@ -477,7 +480,7 @@ private record AttrInfo( int position ) { @NotNull - String fieldName() { + public String fieldName() { if (category != null) { return category.name + '$' + name; } else { diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/TypeFactory.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/TypeFactory.java new file mode 100644 index 000000000..f51373cc8 --- /dev/null +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/TypeFactory.java @@ -0,0 +1,17 @@ +package com.shade.decima.rtti; + +import com.shade.decima.rtti.runtime.ClassTypeInfo; +import com.shade.util.NotNull; + +public interface TypeFactory { + @NotNull + ClassTypeInfo get(@NotNull TypeId id); + + @NotNull + ClassTypeInfo get(@NotNull Class cls); + + @NotNull + default T newInstance(@NotNull Class cls) { + return cls.cast(get(cls).newInstance()); + } +} diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/TypeId.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/TypeId.java new file mode 100644 index 000000000..21999cb33 --- /dev/null +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/TypeId.java @@ -0,0 +1,4 @@ +package com.shade.decima.rtti; + +public interface TypeId { +} diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/AtomTypeInfo.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/AtomTypeInfo.java new file mode 100644 index 000000000..5a9fa3a2f --- /dev/null +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/AtomTypeInfo.java @@ -0,0 +1,14 @@ +package com.shade.decima.rtti.runtime; + +import com.shade.decima.rtti.TypeName; +import com.shade.util.NotNull; + +public record AtomTypeInfo( + @NotNull TypeName.Simple name, + @NotNull Class type +) implements TypeInfo { + @Override + public String toString() { + return name.toString(); + } +} diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/ClassAttrInfo.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/ClassAttrInfo.java new file mode 100644 index 000000000..b297b6db2 --- /dev/null +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/ClassAttrInfo.java @@ -0,0 +1,23 @@ +package com.shade.decima.rtti.runtime; + +import com.shade.util.NotNull; +import com.shade.util.Nullable; + +import java.lang.invoke.VarHandle; + +public record ClassAttrInfo( + @NotNull String name, + @Nullable String category, + @NotNull TypeInfoRef type, + @NotNull VarHandle handle, + int offset, + int flags +) { + public Object get(@NotNull Object object) { + return handle.get(object); + } + + public void set(@NotNull Object object, Object value) { + handle.set(object, value); + } +} diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/ClassBaseInfo.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/ClassBaseInfo.java new file mode 100644 index 000000000..e3a39537a --- /dev/null +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/ClassBaseInfo.java @@ -0,0 +1,9 @@ +package com.shade.decima.rtti.runtime; + +import com.shade.util.NotNull; + +public record ClassBaseInfo( + @NotNull TypeInfoRef type, + int offset +) { +} diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/ClassTypeInfo.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/ClassTypeInfo.java new file mode 100644 index 000000000..e98113f3a --- /dev/null +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/ClassTypeInfo.java @@ -0,0 +1,30 @@ +package com.shade.decima.rtti.runtime; + +import com.shade.decima.rtti.TypeName; +import com.shade.util.NotNull; + +import java.util.List; + +public record ClassTypeInfo( + @NotNull TypeName.Simple name, + @NotNull Class type, + @NotNull List bases, + @NotNull List declaredAttrs, + @NotNull List serializableAttrs +) implements TypeInfo { + @NotNull + @SuppressWarnings({"deprecation"}) + public Object newInstance() { + try { + // The result of this method is called, and we guarantee that it won't throw any checked exceptions. + return type.newInstance(); + } catch (Exception e) { + throw new IllegalStateException("Failed to create instance of " + name, e); + } + } + + @Override + public String toString() { + return name.toString(); + } +} diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/ContainerTypeInfo.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/ContainerTypeInfo.java new file mode 100644 index 000000000..a4279e9bb --- /dev/null +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/ContainerTypeInfo.java @@ -0,0 +1,17 @@ +package com.shade.decima.rtti.runtime; + +import com.shade.decima.rtti.TypeName; +import com.shade.util.NotNull; + +import java.lang.reflect.Type; + +public record ContainerTypeInfo( + @NotNull TypeName.Parameterized name, + @NotNull Type type, + @NotNull TypeInfoRef itemType +) implements TypeInfo { + @Override + public String toString() { + return name.toString(); + } +} diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/EnumTypeInfo.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/EnumTypeInfo.java new file mode 100644 index 000000000..03b565805 --- /dev/null +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/EnumTypeInfo.java @@ -0,0 +1,16 @@ +package com.shade.decima.rtti.runtime; + +import com.shade.decima.rtti.TypeName; +import com.shade.util.NotNull; + +public record EnumTypeInfo( + @NotNull TypeName.Simple name, + @NotNull Class> type, + int size, + boolean flags +) implements TypeInfo { + @Override + public String toString() { + return name.toString(); + } +} diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/EnumValueInfo.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/EnumValueInfo.java new file mode 100644 index 000000000..0165f69b1 --- /dev/null +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/EnumValueInfo.java @@ -0,0 +1,6 @@ +package com.shade.decima.rtti.runtime; + +import com.shade.util.NotNull; + +public record EnumValueInfo(@NotNull String name, int value) { +} diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/PointerTypeInfo.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/PointerTypeInfo.java new file mode 100644 index 000000000..d00cc788c --- /dev/null +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/PointerTypeInfo.java @@ -0,0 +1,17 @@ +package com.shade.decima.rtti.runtime; + +import com.shade.decima.rtti.TypeName; +import com.shade.util.NotNull; + +import java.lang.reflect.Type; + +public record PointerTypeInfo( + @NotNull TypeName.Parameterized name, + @NotNull Type type, + @NotNull TypeInfoRef itemType +) implements TypeInfo { + @Override + public String toString() { + return name.toString(); + } +} diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/TypeInfo.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/TypeInfo.java new file mode 100644 index 000000000..7e401e16c --- /dev/null +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/TypeInfo.java @@ -0,0 +1,16 @@ +package com.shade.decima.rtti.runtime; + +import com.shade.decima.rtti.TypeName; +import com.shade.util.NotNull; + +import java.lang.reflect.Type; + +public sealed interface TypeInfo + permits AtomTypeInfo, ClassTypeInfo, ContainerTypeInfo, EnumTypeInfo, PointerTypeInfo { + + @NotNull + TypeName name(); + + @NotNull + Type type(); +} diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/TypeInfoRef.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/TypeInfoRef.java new file mode 100644 index 000000000..6eb4f4539 --- /dev/null +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/runtime/TypeInfoRef.java @@ -0,0 +1,12 @@ +package com.shade.decima.rtti.runtime; + +import com.shade.decima.rtti.TypeName; +import com.shade.util.NotNull; + +public interface TypeInfoRef { + @NotNull + TypeName name(); + + @NotNull + TypeInfo get(); +} diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/serde/ExtraBinaryDataCallback.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/serde/ExtraBinaryDataCallback.java index b28d01ef5..91a1e0ea9 100644 --- a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/serde/ExtraBinaryDataCallback.java +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/serde/ExtraBinaryDataCallback.java @@ -1,10 +1,11 @@ package com.shade.decima.rtti.serde; +import com.shade.decima.rtti.TypeFactory; import com.shade.util.NotNull; import com.shade.util.io.BinaryReader; import java.io.IOException; public interface ExtraBinaryDataCallback { - void deserialize(@NotNull BinaryReader reader, @NotNull T object) throws IOException; + void deserialize(@NotNull BinaryReader reader, @NotNull TypeFactory factory, @NotNull T object) throws IOException; } diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/serde/ExtraBinaryDataHolder.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/serde/ExtraBinaryDataHolder.java index 60e62743c..9d65928ec 100644 --- a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/serde/ExtraBinaryDataHolder.java +++ b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/serde/ExtraBinaryDataHolder.java @@ -1,12 +1,13 @@ package com.shade.decima.rtti.serde; +import com.shade.decima.rtti.TypeFactory; import com.shade.util.NotNull; import com.shade.util.io.BinaryReader; import java.io.IOException; public interface ExtraBinaryDataHolder { - default void deserialize(@NotNull BinaryReader reader) throws IOException { + default void deserialize(@NotNull BinaryReader reader, @NotNull TypeFactory factory) throws IOException { throw new UnsupportedOperationException("Missing callback for '" + getClass().getInterfaces()[0].getSimpleName() + "' required to read extra data at " + reader.position()); diff --git a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/serde/RTTIBinaryReader.java b/modules/decima-rtti/src/main/java/com/shade/decima/rtti/serde/RTTIBinaryReader.java deleted file mode 100644 index a189ec099..000000000 --- a/modules/decima-rtti/src/main/java/com/shade/decima/rtti/serde/RTTIBinaryReader.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.shade.decima.rtti.serde; - -import com.shade.util.NotNull; - -import java.io.IOException; -import java.util.List; - -public interface RTTIBinaryReader { - @NotNull - List read() throws IOException; -}