mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-06 08:38:30 +09:00
async loading (hopefully)
This commit is contained in:
@@ -363,6 +363,11 @@ public class App implements ApplicationListener {
|
||||
|
||||
public static Thread audioManagerThread;
|
||||
|
||||
private volatile Thread postInitLoadingThread;
|
||||
private volatile boolean loadingThreadDone = false;
|
||||
private volatile boolean loadingThreadNoModules = false;
|
||||
private volatile boolean postLoadInitDone = false;
|
||||
|
||||
public static long loadedTime_t;
|
||||
|
||||
public static void updateBogoflops(long iteration) {
|
||||
@@ -723,6 +728,21 @@ public class App implements ApplicationListener {
|
||||
|
||||
if (loadTimer >= showupTime) {
|
||||
firePostInit();
|
||||
}
|
||||
|
||||
// Process resource loading requests from the loading thread
|
||||
CommonResourcePool.INSTANCE.update();
|
||||
|
||||
// When loading thread is done and all resources are loaded, finish init + show title
|
||||
if (loadingThreadDone && CommonResourcePool.INSTANCE.getLoaded()) {
|
||||
if (!postLoadInitDone) {
|
||||
if (loadingThreadNoModules) {
|
||||
postLoadInitDone = true;
|
||||
}
|
||||
else {
|
||||
postLoadInit();
|
||||
}
|
||||
}
|
||||
|
||||
// hand over the scene control to this single class; Terrarum must call
|
||||
// 'AppLoader.getINSTANCE().screen.render(delta)', this is not redundant at all!
|
||||
@@ -1187,6 +1207,8 @@ public class App implements ApplicationListener {
|
||||
shaderColLUT = loadShaderFromClasspath("shaders/default.vert", "shaders/rgbonly.frag");
|
||||
shaderGhastlyWhite = loadShaderFromClasspath("shaders/default.vert", "shaders/ghastlywhite.frag");
|
||||
|
||||
printdbg(this, "CommTex+Shader done at "+((System.nanoTime() - t1) / 1000000000.0)+" seconds");
|
||||
|
||||
// make gamepad(s)
|
||||
if (App.getConfigBoolean("usexinput")) {
|
||||
try {
|
||||
@@ -1255,17 +1277,11 @@ public class App implements ApplicationListener {
|
||||
);
|
||||
Lang.invoke();
|
||||
|
||||
|
||||
|
||||
ModMgr.INSTANCE.invoke(); // invoke Module Manager
|
||||
|
||||
printdbg(this, "Font done at "+((System.nanoTime() - t1) / 1000000000.0)+" seconds");
|
||||
|
||||
fontSmallNumbers = TinyAlphNum.INSTANCE;
|
||||
fontBigNumbers = BigAlphNum.INSTANCE;
|
||||
|
||||
IME.invoke();
|
||||
inputStrober = InputStrober.INSTANCE;
|
||||
|
||||
try {
|
||||
audioDevice = Gdx.audio.newAudioDevice(48000, false);
|
||||
}
|
||||
@@ -1274,7 +1290,43 @@ public class App implements ApplicationListener {
|
||||
System.err.println("[AppLoader] failed to create audio device: Audio device occupied by Exclusive Mode Device? (e.g. ASIO4all)");
|
||||
}
|
||||
|
||||
CommonResourcePool.INSTANCE.loadAll();
|
||||
IME.invoke();
|
||||
inputStrober = InputStrober.INSTANCE;
|
||||
printdbg(this, "IME done (loading thread) at "+((System.nanoTime() - t1) / 1000000000.0)+" seconds");
|
||||
|
||||
// Set GL thread reference for CommonResourcePool dispatch
|
||||
CommonResourcePool.INSTANCE.setGLThread(Thread.currentThread());
|
||||
// Launch loading thread for ModMgr + slow resource loading
|
||||
postInitLoadingThread = new Thread(() -> {
|
||||
ModMgr.INSTANCE.invoke(); // triggers module init block + EntryPoint.invoke() calls
|
||||
|
||||
printdbg(this, "ModMgr done (loading thread) at "+((System.nanoTime() - t1) / 1000000000.0)+" seconds");
|
||||
|
||||
|
||||
if (ModMgr.INSTANCE.getModuleInfo().isEmpty()) {
|
||||
loadingThreadNoModules = true;
|
||||
loadingThreadDone = true;
|
||||
return;
|
||||
}
|
||||
|
||||
CommonResourcePool.INSTANCE.loadAllSlowly();
|
||||
|
||||
printdbg(this, "loadAllSlowly done (loading thread) at "+((System.nanoTime() - t1) / 1000000000.0)+" seconds");
|
||||
|
||||
loadingThreadDone = true;
|
||||
}, "Terrarum-PostInitLoader");
|
||||
postInitLoadingThread.start();
|
||||
|
||||
long t2 = System.nanoTime();
|
||||
double tms = (t2 - t1) / 1000000000.0;
|
||||
printdbg(this, "PostInit done; took "+tms+" seconds");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on the GL thread after the loading thread finishes and all resources are loaded.
|
||||
*/
|
||||
private void postLoadInit() {
|
||||
long t1 = System.nanoTime();
|
||||
|
||||
// check if selected IME is accessible; if not, set selected IME to none
|
||||
String selectedIME = getConfigString("inputmethod");
|
||||
@@ -1282,18 +1334,8 @@ public class App implements ApplicationListener {
|
||||
setConfig("inputmethod", "none");
|
||||
}
|
||||
|
||||
if (ModMgr.INSTANCE.getModuleInfo().isEmpty()) {
|
||||
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
printdbg(this, "all modules loaded successfully");
|
||||
|
||||
|
||||
// test print
|
||||
if (IS_DEVELOPMENT_BUILD) {
|
||||
System.out.println("[App] Test printing every registered item");
|
||||
@@ -1302,7 +1344,6 @@ public class App implements ApplicationListener {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// create tile atlas
|
||||
printdbg(this, "Making terrain textures...");
|
||||
@@ -1313,6 +1354,7 @@ public class App implements ApplicationListener {
|
||||
throw new Error("TileMaker failed to load", e);
|
||||
}
|
||||
|
||||
printdbg(this, "TileMaker done at "+((System.nanoTime() - t1) / 1000000000.0)+" seconds");
|
||||
|
||||
audioBufferSize = getConfigInt("audio_buffer_size");
|
||||
audioMixer = new AudioMixer();
|
||||
@@ -1322,8 +1364,11 @@ public class App implements ApplicationListener {
|
||||
audioManagerThread.setPriority(MAX_PRIORITY); // higher = more predictable; audio delay is very noticeable so it gets high priority
|
||||
audioManagerThread.start();
|
||||
|
||||
printdbg(this, "AudioEngine done at "+((System.nanoTime() - t1) / 1000000000.0)+" seconds");
|
||||
|
||||
Terrarum.initialise();
|
||||
|
||||
printdbg(this, "TerrarumInit done at "+((System.nanoTime() - t1) / 1000000000.0)+" seconds");
|
||||
|
||||
// if there is a predefined screen, open that screen after my init process
|
||||
if (injectScreen != null) {
|
||||
@@ -1333,14 +1378,11 @@ public class App implements ApplicationListener {
|
||||
IngameRenderer.initialise();
|
||||
}
|
||||
|
||||
|
||||
hasUpdate = CheckUpdate.INSTANCE.hasUpdate();
|
||||
printdbg(this, "Has update: " + hasUpdate);
|
||||
|
||||
|
||||
long t2 = System.nanoTime();
|
||||
double tms = (t2 - t1) / 1000000000.0;
|
||||
printdbg(this, "PostInit done; took "+tms+" seconds");
|
||||
postLoadInitDone = true;
|
||||
printdbg(this, "PostLoadInit done; took "+((System.nanoTime() - t1) / 1000000000.0)+" seconds");
|
||||
}
|
||||
|
||||
public static void reloadAudioProcessor(int bufferSize) {
|
||||
|
||||
@@ -4,8 +4,13 @@ import com.badlogic.gdx.graphics.Texture
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion
|
||||
import com.badlogic.gdx.utils.Disposable
|
||||
import com.badlogic.gdx.utils.Queue
|
||||
import net.torvald.terrarum.App.printdbg
|
||||
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
|
||||
import net.torvald.unsafe.UnsafePtr
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2019-03-10.
|
||||
@@ -13,12 +18,41 @@ import net.torvald.unsafe.UnsafePtr
|
||||
object CommonResourcePool {
|
||||
|
||||
private val loadingList = Queue<ResourceLoadingDescriptor>()
|
||||
private val pool = HashMap<String, Any>()
|
||||
private val pool = ConcurrentHashMap<String, Any>()
|
||||
private val poolKillFun = HashMap<String, ((Any) -> Unit)?>()
|
||||
//private val typesMap = HashMap<String, Class<*>>()
|
||||
private var loadCounter = -1 // using counters so that the loading can be done on separate thread (gg if the asset requires GL context to be loaded)
|
||||
val loaded: Boolean // see if there's a thing to load
|
||||
get() = loadCounter == 0
|
||||
val loaded: Boolean
|
||||
get() = loadCounter <= 0 && slowLoadingRemaining.get() == 0
|
||||
|
||||
@Volatile private var glThread: Thread? = null
|
||||
|
||||
private val glDispatchQueue = ConcurrentLinkedQueue<Pair<List<ResourceLoadingDescriptor>, CountDownLatch>>()
|
||||
private val glRunnableQueue = ConcurrentLinkedQueue<Pair<() -> Unit, CountDownLatch>>()
|
||||
|
||||
private val slowLoadingQueue = ConcurrentLinkedQueue<ResourceLoadingDescriptor>()
|
||||
private val slowLoadingRemaining = AtomicInteger(0)
|
||||
|
||||
fun setGLThread(thread: Thread) {
|
||||
glThread = thread
|
||||
}
|
||||
|
||||
fun isOnGLThread(): Boolean {
|
||||
return glThread == null || Thread.currentThread() == glThread
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs [block] on the GL thread, blocking the calling thread until it completes.
|
||||
* If already on the GL thread, runs [block] directly.
|
||||
*/
|
||||
fun <T> runOnGLThread(block: () -> T): T {
|
||||
if (isOnGLThread()) return block()
|
||||
var result: Any? = null
|
||||
val latch = CountDownLatch(1)
|
||||
glRunnableQueue.add(Pair({ result = block() }, latch))
|
||||
latch.await()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return result as T
|
||||
}
|
||||
|
||||
init {
|
||||
addToLoadingList("itemplaceholder_16") {
|
||||
@@ -80,24 +114,84 @@ object CommonResourcePool {
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes the loading list. After the load, the list will be empty
|
||||
* Consumes the loading list. After the load, the list will be empty.
|
||||
* When called from a non-GL thread, dispatches the actual loading to the GL thread and blocks until complete.
|
||||
*/
|
||||
fun loadAll() {
|
||||
if (loaded) return
|
||||
if (loadingList.isEmpty) return
|
||||
|
||||
// Drain the loadingList into a local list
|
||||
val batch = mutableListOf<ResourceLoadingDescriptor>()
|
||||
while (!loadingList.isEmpty) {
|
||||
val (name, loadfun, killfun) = loadingList.removeFirst()
|
||||
batch.add(loadingList.removeFirst())
|
||||
}
|
||||
|
||||
// no need for the collision checking; quarantine is done when the loading list is being appended
|
||||
/*if (pool.containsKey(name)) {
|
||||
throw IllegalArgumentException("Assets with identifier '$name' already exists.")
|
||||
}*/
|
||||
if (isOnGLThread()) {
|
||||
// Load directly on GL thread
|
||||
for ((name, loadfun, killfun) in batch) {
|
||||
pool[name] = loadfun.invoke()
|
||||
poolKillFun[name] = killfun
|
||||
loadCounter -= 1
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Dispatch to GL thread and block until done
|
||||
val latch = CountDownLatch(1)
|
||||
glDispatchQueue.add(batch to latch)
|
||||
latch.await()
|
||||
}
|
||||
}
|
||||
|
||||
//typesMap[name] = type
|
||||
/**
|
||||
* Moves all pending items in the loading list to the slow loading queue,
|
||||
* then blocks until the GL thread has processed all of them (one per frame via [update]).
|
||||
*/
|
||||
fun loadAllSlowly() {
|
||||
while (!loadingList.isEmpty) {
|
||||
val desc = loadingList.removeFirst()
|
||||
slowLoadingQueue.add(desc)
|
||||
slowLoadingRemaining.incrementAndGet()
|
||||
}
|
||||
// Block until the GL thread has processed all slow items
|
||||
while (slowLoadingRemaining.get() > 0) {
|
||||
Thread.sleep(16)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every frame from App.render() on the GL thread.
|
||||
* Processes dispatched loadAll() requests and one slow-loading item per frame.
|
||||
*/
|
||||
fun update() {
|
||||
// printdbg(this, "CommonResPool update!")
|
||||
// 1. Process all immediate dispatch requests (from loadAll() on background thread)
|
||||
while (true) {
|
||||
val request = glDispatchQueue.poll() ?: break
|
||||
val (batch, latch) = request
|
||||
for ((name, loadfun, killfun) in batch) {
|
||||
pool[name] = loadfun.invoke()
|
||||
poolKillFun[name] = killfun
|
||||
loadCounter -= 1
|
||||
}
|
||||
latch.countDown()
|
||||
}
|
||||
|
||||
// 2. Process all generic GL runnables (from runOnGLThread() on background thread)
|
||||
while (true) {
|
||||
val (runnable, latch) = glRunnableQueue.poll() ?: break
|
||||
runnable()
|
||||
latch.countDown()
|
||||
}
|
||||
|
||||
// 3. Process one item from the slow loading queue (timesliced)
|
||||
val desc = slowLoadingQueue.poll()
|
||||
if (desc != null) {
|
||||
val (name, loadfun, killfun) = desc
|
||||
pool[name] = loadfun.invoke()
|
||||
poolKillFun[name] = killfun
|
||||
|
||||
loadCounter -= 1
|
||||
slowLoadingRemaining.decrementAndGet()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,8 +203,15 @@ object CommonResourcePool {
|
||||
fun getOrPut(name: String, loadfun: () -> Any) = CommonResourcePool.getOrPut(name, loadfun, null)
|
||||
fun getOrPut(name: String, loadfun: () -> Any, killfun: ((Any) -> Unit)?): Any {
|
||||
if (pool.containsKey(name)) return pool[name]!!
|
||||
pool[name] = loadfun.invoke()
|
||||
poolKillFun[name] = killfun
|
||||
if (isOnGLThread()) {
|
||||
pool[name] = loadfun.invoke()
|
||||
poolKillFun[name] = killfun
|
||||
}
|
||||
else {
|
||||
val latch = CountDownLatch(1)
|
||||
glDispatchQueue.add(listOf(ResourceLoadingDescriptor(name, loadfun, killfun)) to latch)
|
||||
latch.await()
|
||||
}
|
||||
return pool[name]!!
|
||||
}
|
||||
|
||||
|
||||
@@ -353,7 +353,6 @@ object ModMgr {
|
||||
val newClassInstance = newClassConstructor.newInstance(/* no args defined */)
|
||||
|
||||
entryPointClasses.add(newClassInstance as ModuleEntryPoint)
|
||||
(newClassInstance as ModuleEntryPoint).invoke()
|
||||
|
||||
printdbg(this, "Module loaded successfully: $moduleName")
|
||||
}
|
||||
@@ -414,7 +413,11 @@ object ModMgr {
|
||||
|
||||
private class ScriptModDisallowedException : RuntimeException("Script Mods disabled")
|
||||
|
||||
operator fun invoke() { }
|
||||
operator fun invoke() {
|
||||
entryPointClasses.forEach { ep ->
|
||||
CommonResourcePool.runOnGLThread { ep.invoke() }
|
||||
}
|
||||
}
|
||||
|
||||
/*fun reloadModules() {
|
||||
loadOrder.forEach {
|
||||
@@ -575,7 +578,7 @@ object ModMgr {
|
||||
val loadedClass = Class.forName(className)
|
||||
val loadedClassConstructor = loadedClass.getConstructor(*constructorTypes)
|
||||
try {
|
||||
return loadedClassConstructor.newInstance(*initArgs) as T
|
||||
return CommonResourcePool.runOnGLThread { loadedClassConstructor.newInstance(*initArgs) as T }
|
||||
}
|
||||
catch (e: InvocationTargetException) {
|
||||
throw InvocationTargetException(e, "Failed to load class '$className' with given constructor arguments")
|
||||
@@ -585,7 +588,7 @@ object ModMgr {
|
||||
val loadedClass = it.loadClass(className)
|
||||
val loadedClassConstructor = loadedClass.getConstructor(*constructorTypes)
|
||||
try {
|
||||
return loadedClassConstructor.newInstance(*initArgs) as T
|
||||
return CommonResourcePool.runOnGLThread { loadedClassConstructor.newInstance(*initArgs) as T }
|
||||
}
|
||||
catch (e: InvocationTargetException) {
|
||||
throw InvocationTargetException(e, "Failed to load class '$className' with given constructor arguments")
|
||||
@@ -601,7 +604,7 @@ object ModMgr {
|
||||
val loadedClass = Class.forName(className)
|
||||
val loadedClassConstructor = loadedClass.getConstructor()
|
||||
try {
|
||||
return loadedClassConstructor.newInstance() as T
|
||||
return CommonResourcePool.runOnGLThread { loadedClassConstructor.newInstance() as T }
|
||||
}
|
||||
catch (e: InvocationTargetException) {
|
||||
throw InvocationTargetException(e, "Failed to load class '$className' with zero constructor arguments")
|
||||
@@ -611,7 +614,7 @@ object ModMgr {
|
||||
val loadedClass = it.loadClass(className)
|
||||
val loadedClassConstructor = loadedClass.getConstructor()
|
||||
try {
|
||||
return loadedClassConstructor.newInstance() as T
|
||||
return CommonResourcePool.runOnGLThread { loadedClassConstructor.newInstance() as T }
|
||||
}
|
||||
catch (e: InvocationTargetException) {
|
||||
throw InvocationTargetException(e, "Failed to load class '$className' with zero constructor arguments")
|
||||
|
||||
@@ -95,18 +95,25 @@ object IME {
|
||||
|
||||
val icons = HashMap<String, TextureRegion>()
|
||||
|
||||
lateinit var layoutLoadingThread: Thread
|
||||
private set
|
||||
|
||||
init {
|
||||
context.getBindings("js").putMember("IMEProvider", IMEProviderDelegate(this))
|
||||
layoutLoadingThread = Thread({
|
||||
context.getBindings("js").putMember("IMEProvider", IMEProviderDelegate(this))
|
||||
|
||||
File(KEYLAYOUT_DIR).listFiles { file, s -> s.endsWith(".$KEYLAYOUT_EXTENSION") }.sortedBy { it.name }.forEach {
|
||||
printdbg(this, "Registering Low layer ${it.nameWithoutExtension.lowercase()}")
|
||||
registerLowLayer(it.nameWithoutExtension.lowercase(), parseKeylayoutFile(it))
|
||||
}
|
||||
File(KEYLAYOUT_DIR).listFiles { _, s -> s.endsWith(".$KEYLAYOUT_EXTENSION") }.sortedBy { it.name }.forEach {
|
||||
printdbg(this, "Registering Low layer ${it.nameWithoutExtension.lowercase()}")
|
||||
registerLowLayer(it.nameWithoutExtension.lowercase(), parseKeylayoutFile(it))
|
||||
}
|
||||
|
||||
File(KEYLAYOUT_DIR).listFiles { file, s -> s.endsWith(".$IME_EXTENSION") }.sortedBy { it.name }.forEach {
|
||||
printdbg(this, "Registering High layer ${it.nameWithoutExtension.lowercase()}")
|
||||
registerHighLayer(it.nameWithoutExtension.lowercase(), parseImeFile(it))
|
||||
}
|
||||
File(KEYLAYOUT_DIR).listFiles { _, s -> s.endsWith(".$IME_EXTENSION") }.sortedBy { it.name }.forEach {
|
||||
printdbg(this, "Registering High layer ${it.nameWithoutExtension.lowercase()}")
|
||||
registerHighLayer(it.nameWithoutExtension.lowercase(), parseImeFile(it))
|
||||
}
|
||||
}, "Terrarum-IMELoader")
|
||||
layoutLoadingThread.isDaemon = true
|
||||
layoutLoadingThread.start()
|
||||
|
||||
|
||||
val iconSheet = TextureRegionPack(AssetCache.getFileHandle("graphics/gui/ime_icons_by_language.tga"), 20, 20)
|
||||
|
||||
Reference in New Issue
Block a user