On desktop, replace Gdx clipboard with a proxy using AWT (#11857)

This commit is contained in:
SomeTroglodyte 2024-06-27 22:26:48 +02:00 committed by GitHub
parent e5e52fe916
commit 1c25839ddb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 59 additions and 6 deletions

View File

@ -0,0 +1,42 @@
package com.unciv.app.desktop
import java.awt.Toolkit
import java.awt.datatransfer.DataFlavor
import java.awt.datatransfer.StringSelection
import java.awt.datatransfer.Clipboard as ClipboardAwt
import com.badlogic.gdx.utils.Clipboard as ClipboardGdx
/**
* A plug-in replacement for Gdx Lwjgl3Clipboard that goes through AWT instead of GLFW and removes the stack size limitation.
*
* Gdx.app.clipboard on desktop is a Lwjgl3Clipboard instance, which allocates a buffer on the stack for UTF8 conversion at the Lwjgl3-GLFW interface.
* The default stack size is a severe limitation, which we **originally** treated by increasing the limit, which can only be done statically at launch time:
```
// 386 is an almost-arbitrary choice from the saves I had at the moment and their GZipped size.
// There must be a reason for lwjgl3 being so stingy, which for me meant to stay conservative.
System.setProperty("org.lwjgl.system.stackSize", "384")
```
* - See [setContents](https://github.com/libgdx/libgdx/blob/master/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Clipboard.java#L40)
* - See [glfwSetClipboardString](https://github.com/LWJGL/lwjgl3/blob/master/modules/lwjgl/glfw/src/generated/java/org/lwjgl/glfw/GLFW.java#L5068-L5077)
* - See [Higher available clipboard size](https://github.com/orgs/LWJGL/discussions/769)
*/
class AwtClipboard : ClipboardGdx {
// A lazy seems to work too, but not keeping a reference when not active is safer (also debuggable while a lazy is not):
private val clipboard: ClipboardAwt
get() = Toolkit.getDefaultToolkit().systemClipboard
override fun hasContents(): Boolean {
return DataFlavor.stringFlavor in clipboard.availableDataFlavors
}
override fun getContents(): String? {
val transferable = clipboard.getContents(null)
if (!transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) return null
return transferable.getTransferData(DataFlavor.stringFlavor) as String
}
override fun setContents(content: String?) {
val selection = StringSelection(content) // Yes this supports null
clipboard.setContents(selection, selection)
}
}

View File

@ -36,10 +36,6 @@ internal object DesktopLauncher {
// Solves a rendering problem in specific GPUs and drivers.
// For more info see https://github.com/yairm210/Unciv/pull/3202 and https://github.com/LWJGL/lwjgl/issues/119
System.setProperty("org.lwjgl.opengl.Display.allowSoftwareOpenGL", "true")
// This setting (default 64) limits clipboard transfers. Value in kB!
// 386 is an almost-arbitrary choice from the saves I had at the moment and their GZipped size.
// There must be a reason for lwjgl3 being so stingy, which for me meant to stay conservative.
System.setProperty("org.lwjgl.system.stackSize", "384")
val isRunFromJAR = DesktopLauncher.javaClass.`package`.specificationVersion != null
ImagePacker.packImages(isRunFromJAR)

View File

@ -6,10 +6,18 @@ import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration
import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio
import com.badlogic.gdx.backends.lwjgl3.audio.OpenALMusic
import com.badlogic.gdx.utils.Array
import com.badlogic.gdx.utils.Clipboard
import com.unciv.ui.audio.MusicController
/**
* Problem: Not all exceptions playing Music can be caught on the desktop platform using a try-catch around the play method.
* ### Notes
* - Since this is the one replacement wrapping Lwjgl3Application that will be running Unciv-desktop, and we want to replace Gdx.app.clipboard,
* the hook for [AwtClipboard] is implemented here as [getClipboard] override.
* - ***This class is never properly initialized before use!*** (because the super constructor runs until the game is quit.)
* Therefore, fields must be initialized on-demand or from upstream UncivGame
*
* ### Problem
* Not all exceptions playing Music can be caught on the desktop platform using a try-catch around the play method.
* Unciv 3.17.13 to 4.0.5 all exacerbated the problem due to using Music from various threads - and Gdx documents it isn't threadsafe.
* But even with that fixed, music streams can have codec failures _after_ the first buffer's worth of data, so the problem is only mitigated.
*
@ -18,7 +26,7 @@ import com.unciv.ui.audio.MusicController
*
* This catches those Exceptions and reports them through a callback mechanism, and also provides a callback from the app loop
* that allows MusicController to make its Music calls on a thread guaranteed to be safe for OpenALMusic.
* #
*
* ### Approach:
* - Subclass [OpenALLwjgl3Audio] overriding [update][OpenALLwjgl3Audio.update] with equivalent code catching any Exceptions and Errors
* - Get the framework to use the subclassed Audio by overriding Lwjgl3ApplicationBase.createAudio
@ -46,6 +54,7 @@ class HardenGdxAudio(
) : Lwjgl3Application(game, config) {
private var updateCallback: (()->Unit)? = null
private var exceptionHandler: ((Throwable, Music)->Unit)? = null
private lateinit var awtClipboard: AwtClipboard // normal initialization won't run, including initializing lazy delegates!
/** Hooks part 1
*
@ -69,6 +78,12 @@ class HardenGdxAudio(
this.exceptionHandler = exceptionHandler
}
/** This redirects `Gdx.app.clipboard` on desktop to our [AwtClipboard] replacement */
override fun getClipboard(): Clipboard {
if (!::awtClipboard.isInitialized) awtClipboard = AwtClipboard()
return awtClipboard
}
/**
* Desktop implementation of the [Audio][com.badlogic.gdx.Audio] interface that
* unlike its superclass catches exceptions and allows passing them into application code for handling.