From 01eefdbd0f62b42466c3e5680b44693eaa5fac84 Mon Sep 17 00:00:00 2001 From: fgnm Date: Sat, 1 Jan 2022 10:16:50 +0100 Subject: [PATCH 1/2] Add artemis reflection Add an easy way to include/exclude classes to Tea reflection (cherry picked from commit 4df1e780c2968cf6692cc1d16e1bbf54dee02e7e) --- .../com/artemis/utils/reflect/Annotation.java | 24 ++ .../emu/com/artemis/utils/reflect/Field.java | 230 ++++++++++++++++++ .../com/badlogic/gdx/utils/reflect/Field.java | 13 +- .../backends/teavm/TeaBuildConfiguration.java | 12 + .../gdx/backends/teavm/TeaBuilder.java | 34 ++- .../backends/web/WebBuildConfiguration.java | 2 + tools/generator/core/build.gradle | 1 + 7 files changed, 308 insertions(+), 8 deletions(-) create mode 100644 backends/backend-teavm/emu/com/artemis/utils/reflect/Annotation.java create mode 100644 backends/backend-teavm/emu/com/artemis/utils/reflect/Field.java diff --git a/backends/backend-teavm/emu/com/artemis/utils/reflect/Annotation.java b/backends/backend-teavm/emu/com/artemis/utils/reflect/Annotation.java new file mode 100644 index 00000000..b34a81ee --- /dev/null +++ b/backends/backend-teavm/emu/com/artemis/utils/reflect/Annotation.java @@ -0,0 +1,24 @@ +package com.artemis.utils.reflect; + +/** Provides information about, and access to, an annotation of a field, class or interface. + * @author dludwig */ +public final class Annotation { + + private java.lang.annotation.Annotation annotation; + + Annotation (java.lang.annotation.Annotation annotation) { + this.annotation = annotation; + } + + @SuppressWarnings("unchecked") + public T getAnnotation (Class annotationType) { + if (annotation.annotationType().equals(annotationType)) { + return (T) annotation; + } + return null; + } + + public Class getAnnotationType () { + return annotation.annotationType(); + } +} \ No newline at end of file diff --git a/backends/backend-teavm/emu/com/artemis/utils/reflect/Field.java b/backends/backend-teavm/emu/com/artemis/utils/reflect/Field.java new file mode 100644 index 00000000..4d47b348 --- /dev/null +++ b/backends/backend-teavm/emu/com/artemis/utils/reflect/Field.java @@ -0,0 +1,230 @@ +package com.artemis.utils.reflect; + +import com.badlogic.gdx.utils.reflect.ArrayReflection; +import com.badlogic.gdx.utils.reflect.ReflectionException; +import com.github.xpenatan.gdx.backends.teavm.plugins.TeaReflectionSupplier; +import com.github.xpenatan.gdx.backends.teavm.util.GenericTypeProvider; +import org.teavm.metaprogramming.*; +import org.teavm.metaprogramming.reflect.ReflectField; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +@CompileTime +public final class Field { + + private final java.lang.reflect.Field field; + + Field (java.lang.reflect.Field field) { + this.field = field; + } + + /** Returns the name of the field. */ + public String getName () { + return field.getName(); + } + + /** Returns a Class object that identifies the declared type for the field. */ + public Class getType () { + return field.getType(); + } + + /** Returns the Class object representing the class or interface that declares the field. */ + public Class getDeclaringClass () { + return field.getDeclaringClass(); + } + + public boolean isAccessible () { + return true; + } + + public void setAccessible (boolean accessible) { + } + + /** Return true if the field does not include any of the {@code private}, {@code protected}, or {@code public} modifiers. */ + public boolean isDefaultAccess () { + return !isPrivate() && !isProtected() && !isPublic(); + } + + /** Return true if the field includes the {@code final} modifier. */ + public boolean isFinal () { + // TODO +// return Modifier.isFinal(field.getModifiers()); + return false; + } + + /** Return true if the field includes the {@code private} modifier. */ + public boolean isPrivate () { + // TODO +// return Modifier.isPrivate(field.getModifiers()); + return false; + } + + /** Return true if the field includes the {@code protected} modifier. */ + public boolean isProtected () { + // TODO +// return Modifier.isProtected(field.getModifiers()); + return false; + } + + /** Return true if the field includes the {@code public} modifier. */ + public boolean isPublic () { + // TODO +// return Modifier.isPublic(field.getModifiers()); + return false; + } + + /** Return true if the field includes the {@code static} modifier. */ + public boolean isStatic () { + // TODO +// return Modifier.isStatic(field.getModifiers()); + return false; + } + + /** Return true if the field includes the {@code transient} modifier. */ + public boolean isTransient () { + // TODO +// return Modifier.isTransient(field.getModifiers()); + return false; + } + + /** Return true if the field includes the {@code volatile} modifier. */ + public boolean isVolatile () { + // TODO +// return Modifier.isVolatile(field.getModifiers()); + return false; + } + + /** Return true if the field is a synthetic field. */ + public boolean isSynthetic () { + return field.isSynthetic(); + } + + private static Class getActualType(Type actualType) { + if (actualType instanceof Class) + return (Class) actualType; + else if (actualType instanceof ParameterizedType) + return (Class) ((ParameterizedType) actualType).getRawType(); + else if (actualType instanceof GenericArrayType) { + Type componentType = ((GenericArrayType) actualType).getGenericComponentType(); + if (componentType instanceof Class) + return ArrayReflection.newInstance((Class) componentType, 0).getClass(); + } + return null; + } + + @Meta + public static native Class getElementType(Class type, String fieldName, int index); + private static void getElementType(ReflectClass cls, Value fieldNameValue, Value indexValue) { + String name = cls.getName(); + if(!TeaReflectionSupplier.containsReflection(name)) { + Metaprogramming.unsupportedCase(); + return; + } + ClassLoader classLoader = Metaprogramming.getClassLoader(); + GenericTypeProvider genericTypeProvider = new GenericTypeProvider(classLoader); + + boolean found = false; + Value result = Metaprogramming.lazy(() -> null); + for (ReflectField field : cls.getDeclaredFields()) { + java.lang.reflect.Field javaField = genericTypeProvider.findField(field); + Type genericType = javaField.getGenericType(); + if (genericType instanceof ParameterizedType) { + Type[] actualTypes = ((ParameterizedType) genericType).getActualTypeArguments(); + + if(actualTypes!= null) { + for(int i = 0; i < actualTypes.length; i++) { + Class actualType = getActualType(actualTypes[i]); + if(actualType == null) + continue; + found = true; + final int index = i; + + String fieldName = field.getName(); + Value existing = result; + result = Metaprogramming.lazy(() -> { + if(index == indexValue.get()) { + if(fieldName.equals(fieldNameValue.get())) { + return actualType; + } + } + return existing.get(); + }); + } + } + } + } + if(!found) { + Metaprogramming.unsupportedCase(); + return; + } + Value type = result; + Metaprogramming.exit(() -> type.get()); + } + + + /** If the type of the field is parameterized, returns the Class object representing the parameter type at the specified index, + * null otherwise. */ + public Class getElementType (int index) { + Class declaringClass = field.getDeclaringClass(); + return getElementType(declaringClass, field.getName(), index); + } + + public T getAnnotation(Class annotationClass) { + final Annotation declaredAnnotation = getDeclaredAnnotation(annotationClass); + return declaredAnnotation != null ? declaredAnnotation.getAnnotation(annotationClass) : null; + } + + /** Returns true if the field includes an annotation of the provided class type. */ + public boolean isAnnotationPresent (Class annotationType) { + return field.isAnnotationPresent(annotationType); + } + + /** Returns an array of {@link Annotation} objects reflecting all annotations declared by this field, + * or an empty array if there are none. Does not include inherited annotations. */ + public Annotation[] getDeclaredAnnotations () { + java.lang.annotation.Annotation[] annotations = field.getDeclaredAnnotations(); + Annotation[] result = new Annotation[annotations.length]; + for (int i = 0; i < annotations.length; i++) { + result[i] = new Annotation(annotations[i]); + } + return result; + } + + /** Returns an {@link Annotation} object reflecting the annotation provided, or null of this field doesn't + * have such an annotation. This is a convenience function if the caller knows already which annotation + * type he's looking for. */ + public Annotation getDeclaredAnnotation (Class annotationType) { + java.lang.annotation.Annotation[] annotations = field.getDeclaredAnnotations(); + if (annotations == null) { + return null; + } + for (java.lang.annotation.Annotation annotation : annotations) { + if (annotation.annotationType().equals(annotationType)) { + return new Annotation(annotation); + } + } + return null; + } + + /** Returns the value of the field on the supplied object. + * @throws IllegalAccessException + * @throws IllegalArgumentException */ + public Object get (Object obj) throws IllegalArgumentException, IllegalAccessException { + return field.get(obj); + } + + /** Sets the value of the field on the supplied object. + * @throws ReflectionException */ + public void set (Object obj, Object value) throws ReflectionException { + try { + field.set(obj, value); + } catch (IllegalArgumentException e) { + throw new ReflectionException("Argument not valid for field: " + getName(), e); + } catch (IllegalAccessException e) { + throw new ReflectionException("Illegal access to field: " + getName(), e); + } + } + +} + diff --git a/backends/backend-teavm/emu/com/badlogic/gdx/utils/reflect/Field.java b/backends/backend-teavm/emu/com/badlogic/gdx/utils/reflect/Field.java index 2baca36a..fe4ba90e 100644 --- a/backends/backend-teavm/emu/com/badlogic/gdx/utils/reflect/Field.java +++ b/backends/backend-teavm/emu/com/badlogic/gdx/utils/reflect/Field.java @@ -208,10 +208,15 @@ public Object get (Object obj) throws IllegalArgumentException, IllegalAccessExc } /** Sets the value of the field on the supplied object. - * @throws IllegalAccessException - * @throws IllegalArgumentException */ - public void set (Object obj, Object value) throws IllegalArgumentException, IllegalAccessException{ - field.set(obj, value); + * @throws ReflectionException */ + public void set (Object obj, Object value) throws ReflectionException { + try { + field.set(obj, value); + } catch (IllegalArgumentException e) { + throw new ReflectionException("Argument not valid for field: " + getName(), e); + } catch (IllegalAccessException e) { + throw new ReflectionException("Illegal access to field: " + getName(), e); + } } } diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaBuildConfiguration.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaBuildConfiguration.java index 97e20b6e..a275ce06 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaBuildConfiguration.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaBuildConfiguration.java @@ -17,6 +17,8 @@ public class TeaBuildConfiguration extends WebBuildConfiguration { public String mainApplicationClass; public String webappPath; public final Array additionalClasspath = new Array<>(); + public final Array reflectionInclude = new Array<>(); + public final Array reflectionExclude = new Array<>(); @Override public String getMainClass() { @@ -49,6 +51,16 @@ public boolean minifying() { return obfuscate; } + @Override + public Array getReflectionInclude() { + return reflectionInclude; + } + + @Override + public Array getReflectionExclude() { + return reflectionExclude; + } + @Override public void assetsClasspath(Array classPaths) { } diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaBuilder.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaBuilder.java index a72cad63..3ae23cd9 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaBuilder.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaBuilder.java @@ -16,10 +16,13 @@ import org.teavm.vm.*; import java.io.File; +import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.util.List; import java.util.Properties; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; /** @@ -32,6 +35,29 @@ public static void build(WebBuildConfiguration configuration) { public static void build(WebBuildConfiguration configuration, TeaProgressListener progressListener) { addDefaultReflectionClasses(); + for (URL classPath : configuration.getAdditionalClasspath()) { + try { + ZipInputStream zip = new ZipInputStream(classPath.openStream()); + for (ZipEntry entry = zip.getNextEntry(); entry != null; entry = zip.getNextEntry()) { + if (!entry.isDirectory() && entry.getName().endsWith(".class")) { + // This ZipEntry represents a class. Now, what class does it represent? + String className = entry.getName().replace('/', '.'); // including ".class" + String name = className.substring(0, className.length() - ".class".length()); + boolean add = false; + for (String toInclude : configuration.getReflectionInclude()) { + if (name.startsWith(toInclude)) add = true; + } + for (String toExclude : configuration.getReflectionExclude()) { + if (name.startsWith(toExclude)) add = false; + } + + if (add) TeaReflectionSupplier.addReflectionClass(name); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } URL[] urLs = ((URLClassLoader) (Thread.currentThread().getContextClassLoader())).getURLs(); @@ -90,7 +116,7 @@ public static void build(WebBuildConfiguration configuration, TeaProgressListene System.out.println("targetDirectory: " + webappDirectory); - File setTargetDirectory = new File(webappDirectory + "\\" + webappName + "\\" + "teavm"); + File setTargetDirectory = new File(webappDirectory + File.separator + webappName + File.separator + "teavm"); String setTargetFileName = "app.js"; boolean setMinifying = configuration.minifying(); String mainClass = configuration.getMainClass(); @@ -125,7 +151,7 @@ public static void build(WebBuildConfiguration configuration, TeaProgressListene WebBuildConfiguration.logHeader("Copying Assets"); - String assetsOutputPath = webappDirectory + "\\" + webappName + "\\assets"; + String assetsOutputPath = webappDirectory + File.separator + webappName + File.separator + "assets"; Array assetsPaths = new Array<>(); Array classPathAssetsFiles = new Array<>(); assetsDefaultClasspath(classPathAssetsFiles); @@ -370,7 +396,7 @@ enum ACCEPT_STATE { } public interface TeaProgressListener { - public void onSuccess(boolean success); - public void onProgress(float progress); + void onSuccess(boolean success); + void onProgress(float progress); } } diff --git a/backends/backend-web/src/com/github/xpenatan/gdx/backends/web/WebBuildConfiguration.java b/backends/backend-web/src/com/github/xpenatan/gdx/backends/web/WebBuildConfiguration.java index dbcd42b8..bf925ea4 100644 --- a/backends/backend-web/src/com/github/xpenatan/gdx/backends/web/WebBuildConfiguration.java +++ b/backends/backend-web/src/com/github/xpenatan/gdx/backends/web/WebBuildConfiguration.java @@ -60,4 +60,6 @@ public boolean acceptClasspath(URL url) { public abstract boolean minifying(); + public abstract Array getReflectionInclude(); + public abstract Array getReflectionExclude(); } diff --git a/tools/generator/core/build.gradle b/tools/generator/core/build.gradle index e1077077..d067f278 100644 --- a/tools/generator/core/build.gradle +++ b/tools/generator/core/build.gradle @@ -17,6 +17,7 @@ dependencies { implementation "com.badlogicgames.gdx:gdx:$project.gdxVersion" implementation "com.badlogicgames.gdx:gdx-platform:$project.gdxVersion:natives-desktop" implementation project(":backends:backend-teavm") + implementation project(":extensions:gdx-freetype-teavm") // implementation project(":backends:backend-dragome") } From d2579294048e6f125d42c150529104a38f3a79d4 Mon Sep 17 00:00:00 2001 From: fgnm Date: Sat, 1 Jan 2022 11:11:14 +0100 Subject: [PATCH 2/2] Add HyperLap2D test configuration (cherry picked from commit 157d5895715d73a6d832cd24b9163e6eb81f8148) --- .../gdx/backends/teavm/TeaBuilder.java | 13 +++++-- .../gdx/examples/teavm/HyperLap2DTest.java | 38 +++++++++++++++++++ tools/generator/core/build.gradle | 1 + 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 examples/core/teavm/src/main/java/com/github/xpenatan/gdx/examples/teavm/HyperLap2DTest.java diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaBuilder.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaBuilder.java index 3ae23cd9..0fd06099 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaBuilder.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaBuilder.java @@ -38,6 +38,7 @@ public static void build(WebBuildConfiguration configuration, TeaProgressListene for (URL classPath : configuration.getAdditionalClasspath()) { try { ZipInputStream zip = new ZipInputStream(classPath.openStream()); + WebBuildConfiguration.logHeader("Automatic Reflection Include"); for (ZipEntry entry = zip.getNextEntry(); entry != null; entry = zip.getNextEntry()) { if (!entry.isDirectory() && entry.getName().endsWith(".class")) { // This ZipEntry represents a class. Now, what class does it represent? @@ -51,7 +52,10 @@ public static void build(WebBuildConfiguration configuration, TeaProgressListene if (name.startsWith(toExclude)) add = false; } - if (add) TeaReflectionSupplier.addReflectionClass(name); + if (add) { + WebBuildConfiguration.log("Include class: " + name); + TeaReflectionSupplier.addReflectionClass(name); + } } } } catch (IOException e) { @@ -122,7 +126,8 @@ public static void build(WebBuildConfiguration configuration, TeaProgressListene String mainClass = configuration.getMainClass(); TeaClassTransformer.applicationListener = configuration.getApplicationListenerClass(); - File setCacheDirectory = new File("C:\\TeaVMCache"); + String tmpdir = System.getProperty("java.io.tmpdir"); + File setCacheDirectory = new File(tmpdir + File.separator + "TeaVMCache"); boolean setIncremental = false; tool.setClassLoader(classLoader); @@ -142,8 +147,8 @@ public static void build(WebBuildConfiguration configuration, TeaProgressListene tool.setTargetType(TeaVMTargetType.JAVASCRIPT); Properties properties = tool.getProperties(); - properties.put("teavm.libgdx.fsJsonPath", webappDirectory + "\\" + webappName + "\\" + "filesystem.json"); - properties.put("teavm.libgdx.warAssetsDirectory", webappDirectory + "\\" + webappName + "\\" + "assets"); + properties.put("teavm.libgdx.fsJsonPath", webappDirectory + File.separator + webappName + File.separator + "filesystem.json"); + properties.put("teavm.libgdx.warAssetsDirectory", webappDirectory + File.separator + webappName + File.separator + "assets"); Array webappAssetsFiles = new Array<>(); webappAssetsFiles.add(webappName); diff --git a/examples/core/teavm/src/main/java/com/github/xpenatan/gdx/examples/teavm/HyperLap2DTest.java b/examples/core/teavm/src/main/java/com/github/xpenatan/gdx/examples/teavm/HyperLap2DTest.java new file mode 100644 index 00000000..0fa9df5d --- /dev/null +++ b/examples/core/teavm/src/main/java/com/github/xpenatan/gdx/examples/teavm/HyperLap2DTest.java @@ -0,0 +1,38 @@ +package com.github.xpenatan.gdx.examples.teavm; + +import com.github.xpenatan.gdx.backends.teavm.TeaBuildConfiguration; +import com.github.xpenatan.gdx.backends.teavm.TeaBuilder; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +public class HyperLap2DTest { + + public static void main(String[] args) { + URL appJarAppUrl = null; + try { + appJarAppUrl = new File("/home/kalculon/Games/HyperRunner2/lwjgl3/build/libs/HyperRunner-0.0.1.jar").toURI().toURL(); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + + TeaBuildConfiguration teaBuildConfiguration = new TeaBuildConfiguration(); + teaBuildConfiguration.assetsPath.add(new File("/home/kalculon/Games/HyperRunner2/assets")); + teaBuildConfiguration.webappPath = new File("/opt/lampp/htdocs/teavm").getAbsolutePath(); + teaBuildConfiguration.obfuscate = false; + teaBuildConfiguration.mainApplicationClass = "games.rednblack.hyperrunner.HyperRunner"; + teaBuildConfiguration.additionalClasspath.add(appJarAppUrl); + + teaBuildConfiguration.reflectionInclude.add("games.rednblack.editor.renderer"); + teaBuildConfiguration.reflectionInclude.add("games.rednblack.h2d.extension.spine"); + teaBuildConfiguration.reflectionInclude.add("games.rednblack.h2d.extension.talos"); + teaBuildConfiguration.reflectionInclude.add("games.rednblack.hyperrunner"); + teaBuildConfiguration.reflectionInclude.add("com.artemis"); + teaBuildConfiguration.reflectionInclude.add("com.talosvfx.talos.runtime"); + + teaBuildConfiguration.reflectionExclude.add("com.artemis.annotation"); + teaBuildConfiguration.reflectionExclude.add("com.artemis.utils.reflect"); + TeaBuilder.build(teaBuildConfiguration); + } +} diff --git a/tools/generator/core/build.gradle b/tools/generator/core/build.gradle index d067f278..defd3f93 100644 --- a/tools/generator/core/build.gradle +++ b/tools/generator/core/build.gradle @@ -16,6 +16,7 @@ dependencies { implementation "com.badlogicgames.gdx:gdx:$project.gdxVersion" implementation "com.badlogicgames.gdx:gdx-platform:$project.gdxVersion:natives-desktop" + implementation project(":backends:backend-web") implementation project(":backends:backend-teavm") implementation project(":extensions:gdx-freetype-teavm") // implementation project(":backends:backend-dragome")