Skip to content

Commit da60ea2

Browse files
Upload test code for creating sub classes with Luax
1 parent e477d3c commit da60ea2

File tree

14 files changed

+269
-12
lines changed

14 files changed

+269
-12
lines changed

assets/new_screen.lua

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
local Gdx = luajava.bindClass("com.badlogic.gdx.Gdx")
2+
local Input = luajava.bindClass("com.badlogic.gdx.Input")
3+
local Funkin = luajava.bindClass('me.stringfromjava.funkin.Funkin')
4+
local FunkinScreen = luax.getClass('me.stringfromjava.funkin.backend.display.FunkinScreen')
5+
local Color = luajava.bindClass('com.badlogic.gdx.graphics.Color')
6+
7+
local CustomLuaScreen = FunkinScreen({
8+
__init__ = function(self)
9+
print('new screen has been made and switched to! (printed from the constructor)')
10+
end,
11+
12+
show = function(self)
13+
self.super:show()
14+
print('the screen is now being shown!')
15+
16+
self.bgColor = Color.new(1, 0, 0, 1)
17+
end
18+
})
19+
20+
Funkin.Signals.postRender.add(luajava.createProxy('java.lang.Runnable'), {
21+
run = function()
22+
if Gdx.input:isKeyPressed(Input.Keys.N) then
23+
Funkin:playSound('shared/sounds/gameplay/gameover/fnf_loss_sfx-pixel-pico.ogg')
24+
Funkin:switchScreen(CustomLuaScreen.new())
25+
end
26+
end
27+
})
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
--[[
2+
Pre-ran script that gets executed when the game first launches.
3+
4+
This script pretty much defines and creates the Luax API to be used in the
5+
modder's (or our) scripts.
6+
]]--
7+
8+
local Reflect = luajava.bindClass('me.stringfromjava.funkin.backend.Reflect')
9+
local ByteUtil = luajava.bindClass('me.stringfromjava.funkin.util.ByteUtil')
10+
local Arrays = luajava.bindClass('java.util.Arrays')
11+
12+
--[[
13+
The module that holds every function which pertains to the
14+
FNF:JE Luax API.
15+
]]--
16+
luax = {}
17+
18+
local function createSuperWrapper(instance, methodDefinitions)
19+
local superTable = {}
20+
21+
for methodName, func in pairs(methodDefinitions) do
22+
if type(func) == 'function' and methodName ~= '__init__' then
23+
superTable[methodName] = function(_, ...)
24+
local argsTable = {...}
25+
local javaArgsArray = Arrays:copyOf(argsTable, #argsTable, luajava.bindClass('java.lang.Object'))
26+
return Reflect:invokeBaseMethod(instance, methodName, javaArgsArray)
27+
end
28+
end
29+
end
30+
return superTable
31+
end
32+
33+
function luax.getClass(classPath)
34+
local isFinal = Reflect:isClassFinal(classPath)
35+
36+
-- If it is a utility class, then return the class
37+
-- using the luajava module.
38+
if isFinal then
39+
return luajava.bindClass(classPath)
40+
end
41+
42+
local subclass = ByteUtil:getSubclass(classPath)
43+
44+
return setmetatable({}, {
45+
__call = function(factory, methodDefinitions, ...)
46+
-- Prepare arguments for Java constructor (need to be packaged as a Java array)
47+
-- Note: In LuaJ, '...' are passed as a Varargs, but we collect them into a Lua table first.
48+
local argsTable = {...}
49+
local javaArgsArray = Arrays:copyOf(argsTable, #argsTable, luajava.bindClass("java.lang.Object[]"))
50+
51+
local luaDelegateProxy = luajava.proxy(LuaMethodHandlerInterface:getName(), {
52+
53+
-- Universal Method Router: Maps overridden methods to user's functions.
54+
handleMethod = function(self, methodName, args)
55+
local customMethod = methodDefinitions[methodName]
56+
if customMethod then
57+
-- Call the user's function, passing the Lua table (self) and args.
58+
return customMethod(methodDefinitions, unpack(args))
59+
else
60+
-- Returns nil: Triggers Java to call the original super method.
61+
return nil
62+
end
63+
end,
64+
65+
-- Constructor Invoker: Runs the user's custom __init__.
66+
invokeConstructor = function(self, args)
67+
if methodDefinitions.__init__ then
68+
-- Call the user's custom constructor, passing the instance and arguments.
69+
methodDefinitions.__init__(methodDefinitions, unpack(args))
70+
end
71+
end
72+
})
73+
74+
ConstructorHandler.pendingDelegate:set(luaDelegateProxy)
75+
local javaInstance = subclass.new(unpack(javaArgsArray))
76+
77+
methodDefinitions.javaObject = javaInstance
78+
methodDefinitions.super = createSuperWrapper(javaInstance, methodDefinitions)
79+
80+
-- Clean up the ThreadLocal (although Java's intercept does it, this is a safety measure).
81+
ConstructorHandler.pendingDelegate:remove()
82+
83+
return javaInstance
84+
end
85+
})
86+
end

core/build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ dependencies {
99
api "io.github.libktx:ktx-freetype:$ktxVersion"
1010
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
1111
api "org.mini2Dx:universal-tween-engine:$universalTweenVersion"
12-
implementation "org.luaj:luaj-jse:3.0.1"
12+
implementation 'org.luaj:luaj-jse:3.0.1'
13+
implementation 'net.bytebuddy:byte-buddy:1.15.10'
14+
implementation 'net.bytebuddy:byte-buddy-agent:1.15.10'
1315

1416
if(enableGraalNative == 'true') {
1517
implementation "io.github.berstanio:gdx-svmhelper-annotations:$graalHelperVersion"

core/src/main/java/me/stringfromjava/funkin/Funkin.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import com.badlogic.gdx.audio.Sound;
66
import me.stringfromjava.funkin.audio.FunkinSound;
77
import me.stringfromjava.funkin.backend.display.FunkinScreen;
8-
import me.stringfromjava.funkin.lua.FunkinLua;
8+
import me.stringfromjava.funkin.luax.Luax;
99
import me.stringfromjava.funkin.tween.FunkinTween;
1010
import me.stringfromjava.funkin.backend.system.FunkinSignal;
1111
import me.stringfromjava.funkin.backend.system.Paths;
@@ -72,7 +72,7 @@ public static void initialize(FunkinGame gameInstance) {
7272
game = gameInstance;
7373

7474
FunkinTween.registerAccessors();
75-
FunkinLua.initialize();
75+
Luax.initialize();
7676

7777
initialized = true;
7878
}

core/src/main/java/me/stringfromjava/funkin/FunkinGame.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.badlogic.gdx.audio.Sound;
66
import me.stringfromjava.funkin.backend.display.cache.TextureCache;
77
import me.stringfromjava.funkin.game.InitScreen;
8+
import me.stringfromjava.funkin.luax.Luax;
89
import me.stringfromjava.funkin.tween.FunkinTween;
910

1011
/**
@@ -18,6 +19,11 @@ public class FunkinGame extends Game {
1819

1920
@Override
2021
public void create() {
22+
// Run all preload scripts to configure things such as
23+
// the Luax API.
24+
Luax.executeScript("preload/scripts/luax_api.lua");
25+
Luax.executeScript("new_screen.lua");
26+
2127
setScreen(new InitScreen());
2228
}
2329

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package me.stringfromjava.funkin.backend;
2+
3+
import java.lang.reflect.Method;
4+
import java.lang.reflect.Modifier;
5+
6+
public class Reflect {
7+
8+
public static Object invokeBaseMethod(Object instance, String methodName, Object[] args) throws Exception {
9+
Class<?> baseClass = instance.getClass().getSuperclass();
10+
11+
for (Method m : baseClass.getMethods()) {
12+
if (m.getName().equals(methodName)) {
13+
m.setAccessible(true);
14+
return m.invoke(instance, args);
15+
}
16+
}
17+
throw new NoSuchMethodException("Method " + methodName + " not found on base class " + baseClass.getName());
18+
}
19+
20+
public static boolean isClassFinal(String classPath) {
21+
try {
22+
Class<?> clazz = Class.forName(classPath);
23+
// Uses the java.lang.reflect.Modifier utility
24+
return Modifier.isFinal(clazz.getModifiers());
25+
} catch (ClassNotFoundException e) {
26+
// Treat non-existent class as non-final for safe binding,
27+
// though the user will hit an error later.
28+
return false;
29+
}
30+
}
31+
}

core/src/main/java/me/stringfromjava/funkin/game/menus/TitleScreen.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import me.stringfromjava.funkin.Funkin;
77
import me.stringfromjava.funkin.audio.FunkinSound;
88
import me.stringfromjava.funkin.backend.display.FunkinScreen;
9-
import me.stringfromjava.funkin.lua.FunkinLua;
9+
import me.stringfromjava.funkin.luax.Luax;
1010

1111
public class TitleScreen extends FunkinScreen {
1212

@@ -25,7 +25,7 @@ public void render(float delta) {
2525
super.render(delta);
2626

2727
if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) {
28-
FunkinLua.executeScript("test.lua");
28+
Luax.executeScript("test.lua");
2929
}
3030

3131
if (Gdx.input.isKeyJustPressed(Input.Keys.R)) {

core/src/main/java/me/stringfromjava/funkin/lua/FunkinLua.java renamed to core/src/main/java/me/stringfromjava/funkin/luax/Luax.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
package me.stringfromjava.funkin.lua;
1+
package me.stringfromjava.funkin.luax;
22

3-
import me.stringfromjava.funkin.lua.resource.FunkinLuaResourceFinder;
3+
import me.stringfromjava.funkin.luax.resource.LuaxResourceFinder;
44
import org.luaj.vm2.Globals;
55
import org.luaj.vm2.LuaValue;
66
import org.luaj.vm2.Varargs;
@@ -9,7 +9,7 @@
99
/**
1010
* Global manager and utility class for executing, manipulating and handling Lua scripts.
1111
*/
12-
public final class FunkinLua {
12+
public final class Luax {
1313

1414
private static Globals globals;
1515

@@ -20,7 +20,7 @@ public final class FunkinLua {
2020
*/
2121
public static void initialize() {
2222
globals = JsePlatform.standardGlobals();
23-
globals.finder = new FunkinLuaResourceFinder();
23+
globals.finder = new LuaxResourceFinder();
2424
}
2525

2626
/**
@@ -53,6 +53,6 @@ public static LuaValue loadScript(String path) {
5353
}
5454
}
5555

56-
private FunkinLua() {
56+
private Luax() {
5757
}
5858
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package me.stringfromjava.funkin.luax.exception;
2+
3+
public class NonExistentSuperConstructorCall extends IllegalStateException {
4+
public NonExistentSuperConstructorCall(String s) {
5+
super();
6+
}
7+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package me.stringfromjava.funkin.luax.handler;
2+
3+
import me.stringfromjava.funkin.luax.exception.NonExistentSuperConstructorCall;
4+
import net.bytebuddy.implementation.bind.annotation.AllArguments;
5+
import net.bytebuddy.implementation.bind.annotation.SuperCall;
6+
import net.bytebuddy.implementation.bind.annotation.This;
7+
8+
import java.util.concurrent.Callable;
9+
10+
public final class LuaxConstructorHandler {
11+
12+
private static ThreadLocal<LuaxMethodHandler> pendingDelegate = new ThreadLocal<>();
13+
14+
public static void interceptConstructor(@SuperCall Callable<?> superCall, @This Object proxy, @AllArguments Object[] args) throws Exception {
15+
superCall.call();
16+
17+
LuaxMethodHandler delagate = pendingDelegate.get();
18+
if (delagate == null) {
19+
throw new NonExistentSuperConstructorCall("No super constructor call found for Lua class instantiation.");
20+
}
21+
22+
proxy.getClass().getField("luaxDelegate").set(proxy, delagate);
23+
delagate.invokeConstructor(args);
24+
pendingDelegate.remove();
25+
}
26+
}

0 commit comments

Comments
 (0)