Files
Spine_lab/tools/live/src/GeomProbe2.java
T

271 lines
10 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import java.lang.instrument.Instrumentation;
import java.lang.reflect.*;
import java.net.URLClassLoader;
import java.util.*;
/**
* 只读探针:把 Spine View 面板里每个控件的**真实坐标/尺寸**和父子关系打出来,
* 用来查"列表里的内容跑到面板外面"到底是谁的坐标不对。不改任何状态。
*
* 依据(都不是猜的):
* - Actor 的 9 个 float 字段按 libGDX 声明顺序 = x, y, width, height, originX, originY,
* scaleX, scaleY, rotation(`javap -p dl` 里就是这一串 float);
* - `dl.init()F` = getHeight(面板日志里一直在用),用来交叉验证哪个字段是 height;
* - `eX.V(Z)V` = WidgetGroup.setFillParent、`eX.StackMapTable()V` = layout、
* `et.C(Z)V` = Table.setClip、`do.D()Lhq;` = Group.getChildren(取自字节码反查)。
*/
public final class GeomProbe2 {
static Instrumentation INST;
static ClassLoader L;
static Object STATE;
static Class<?> OG;
static final String[] FLOAT_NAMES = {
"x", "y", "width", "height", "originX", "originY", "scaleX", "scaleY", "rotation"
};
public static void agentmain(String args, Instrumentation inst) {
INST = inst;
try { run(); } catch (Throwable e) { log("失败: " + e); e.printStackTrace(System.err); }
}
static void log(String m) { System.err.println("[geom2] " + m); }
static void run() throws Exception {
for (Class<?> c : INST.getAllLoadedClasses()) {
if (c.getClassLoader() instanceof URLClassLoader) { L = c.getClassLoader(); break; }
}
if (L == null) { log("找不到 URLClassLoader"); return; }
Class<?> panel = null;
int copies = 0;
for (Class<?> c : INST.getAllLoadedClasses()) {
if (!c.getName().equals("com.spineview.ObjectPanel")) continue;
copies++;
if (panel == null) panel = c;
}
log("已加载的 ObjectPanel 份数 = " + copies + " 用 " + panel
+ " (loader=" + (panel == null ? "-" : panel.getClassLoader()) + ")");
if (panel == null) { log("ObjectPanel 没加载"); return; }
Field last = panel.getDeclaredField("LAST_STATE");
last.setAccessible(true);
STATE = last.get(null);
log("LAST_STATE = " + STATE);
if (STATE == null) { return; }
for (Class<?> c : INST.getAllLoadedClasses()) {
if (c.getName().equals("OG")) { OG = c; break; }
}
if (OG == null) { log("OG 没加载"); return; }
Method onRender = null;
for (Method m : OG.getDeclaredMethods()) {
Class<?>[] t = m.getParameterTypes();
if (m.getName().equals("B") && t.length == 1 && t[0] == Runnable.class) { onRender = m; break; }
}
if (onRender == null) { log("找不到 OG.B(Runnable)"); return; }
onRender.setAccessible(true);
onRender.invoke(null, new Runnable() {
@Override public void run() {
try { work(); } catch (Throwable e) { log("work 失败: " + e); e.printStackTrace(System.err); }
}
});
}
static void work() throws Exception {
Object root = state("root");
Object buttons = state("buttons");
Object scroll = state("listScroll");
Object listTable = state("listTable");
Object status = state("status");
log("==================== 面板控件几何 ====================");
dump("root ", root);
dump("buttons ", buttons);
dump("listScroll ", scroll);
dump("listTable ", listTable);
dump("logLabel ", status);
log("---- root 的父链(往上看 4 层,看谁给了多少高度)----");
Object p = parent(root);
for (int i = 0; i < 4 && p != null; i++) {
dump("parent[" + i + "] ", p);
p = parent(p);
}
log("---- listScroll 的父链 ----");
p = parent(scroll);
for (int i = 0; i < 3 && p != null; i++) {
dump("scroll.parent[" + i + "] ", p);
p = parent(p);
}
log("---- ScrollPane 内部状态(widget 是谁、内容多高、滚到哪)----");
dumpScrollPaneFields(scroll);
log("---- listTable 里挂了多少行、第一行/最后一行在哪 ----");
Object children = call(scroll.getClass().getClassLoader().loadClass("do"), listTable, "D");
int n = size(children);
log(" 子控件数 = " + n);
for (int i = 0; i < n; i++) {
if (i > 2 && i < n - 2) continue;
Object child = get(children, i);
dump(" child[" + i + "] ", child);
}
log("---- 关键判定 ----");
Object widget = findWidget(scroll);
log(" ScrollPane 的 widget 是不是 listTable ? " + (widget == listTable)
+ " (widget=" + (widget == null ? "null" : widget.getClass().getName()) + ")");
log(" 两者尺寸: scroll=" + fl(scroll, 2) + "x" + fl(scroll, 3)
+ " listTable=" + fl(listTable, 2) + "x" + fl(listTable, 3));
Object stage = callActor(root, "D"); // Actor.getStage()
if (stage != null) {
log(" stage 尺寸: " + floatOf(stage, "D") + " x " + floatOf(stage, "B"));
}
log("=====================================================");
}
static Object state(String name) throws Exception {
Field f = STATE.getClass().getDeclaredField(name);
f.setAccessible(true);
return f.get(STATE);
}
static void dump(String label, Object actor) {
if (actor == null) { log(label + " = null"); return; }
StringBuilder sb = new StringBuilder();
sb.append(label).append(' ').append(actor.getClass().getName());
int i = 0;
for (Class<?> t = actor.getClass(); t != null && t != Object.class; t = t.getSuperclass()) {
for (Field f : t.getDeclaredFields()) {
if (f.getType() != float.class) continue;
f.setAccessible(true);
try {
String name = i < FLOAT_NAMES.length ? FLOAT_NAMES[i] : "float" + i;
sb.append(' ').append(name).append('=').append(round(f.getFloat(actor)));
i++;
} catch (Throwable ignored) { }
}
if (t.getSuperclass() == null) break;
}
sb.append(" |getHeight()=").append(round(floatOf(actor, "init")));
Object parent = parent(actor);
sb.append(" |parent=").append(parent == null ? "null" : parent.getClass().getName());
log(sb.toString());
}
static void dumpScrollPaneFields(Object pane) {
java.util.TreeSet<String> names = new java.util.TreeSet<String>();
for (Class<?> t = pane.getClass(); t != null && t != Object.class; t = t.getSuperclass()) {
for (Field f : t.getDeclaredFields()) {
if (Modifier.isStatic(f.getModifiers())) continue;
Class<?> type = f.getType();
if (type != float.class && type != int.class && type != boolean.class) continue;
f.setAccessible(true);
try {
Object v = f.get(pane);
if (v instanceof Float) v = Float.valueOf(round(((Float) v).floatValue()));
names.add(t.getSimpleName() + "." + f.getName() + "=" + v);
} catch (Throwable ignored) { }
}
}
StringBuilder sb = new StringBuilder();
for (String s : names) sb.append(' ').append(s);
log(" " + sb);
}
static Object findWidget(Object pane) {
for (Class<?> t = pane.getClass(); t != null && t != Object.class; t = t.getSuperclass()) {
for (Field f : t.getDeclaredFields()) {
if (Modifier.isStatic(f.getModifiers())) continue;
if (f.getType().getName().equals("dl")) {
f.setAccessible(true);
try { return f.get(pane); } catch (Throwable ignored) { }
}
}
}
return null;
}
static Object parent(Object actor) {
try {
for (Method m : actor.getClass().getMethods()) {
if (m.getParameterTypes().length != 0) continue;
if (m.getReturnType().getName().equals("do") && m.getName().length() == 1) {
m.setAccessible(true);
Object v = m.invoke(actor);
if (v != null) return v;
}
}
} catch (Throwable ignored) { }
return null;
}
static Object callActor(Object actor, String name) {
try {
for (Method m : actor.getClass().getMethods()) {
if (!m.getName().equals(name) || m.getParameterTypes().length != 0) continue;
m.setAccessible(true);
return m.invoke(actor);
}
} catch (Throwable ignored) { }
return null;
}
static Object call(Class<?> owner, Object target, String name) {
try {
for (Method m : owner.getMethods()) {
if (!m.getName().equals(name) || m.getParameterTypes().length != 0) continue;
m.setAccessible(true);
return m.invoke(target);
}
} catch (Throwable ignored) { }
return null;
}
static float fl(Object actor, int index) {
int i = 0;
for (Class<?> t = actor.getClass(); t != null && t != Object.class; t = t.getSuperclass()) {
for (Field f : t.getDeclaredFields()) {
if (f.getType() != float.class) continue;
if (i == index) {
f.setAccessible(true);
try { return f.getFloat(actor); } catch (Throwable ignored) { return -1f; }
}
i++;
}
}
return -1f;
}
static float floatOf(Object target, String name) {
try {
for (Method m : target.getClass().getMethods()) {
if (!m.getName().equals(name)) continue;
if (m.getParameterTypes().length != 0 || m.getReturnType() != float.class) continue;
m.setAccessible(true);
return ((Float) m.invoke(target)).floatValue();
}
} catch (Throwable ignored) { }
return -1f;
}
static int size(Object array) {
if (array == null) return 0;
for (Class<?> t = array.getClass(); t != null && t != Object.class; t = t.getSuperclass()) {
for (Field f : t.getDeclaredFields()) {
if (f.getType() != int.class) continue;
f.setAccessible(true);
try { return f.getInt(array); } catch (Throwable ignored) { }
}
}
return 0;
}
static Object get(Object array, int index) {
if (array == null) return null;
for (Class<?> t = array.getClass(); t != null && t != Object.class; t = t.getSuperclass()) {
for (Field f : t.getDeclaredFields()) {
if (f.getType() != Object[].class) continue;
f.setAccessible(true);
try { return ((Object[]) f.get(array))[index]; } catch (Throwable ignored) { }
}
}
return null;
}
static float round(float v) { return Math.round(v * 100f) / 100f; }
}