Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Common Win32 gamepad API + test #1355

Merged
merged 5 commits into from
Feb 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions kmem/src/androidMain/kotlin/com/soywiz/kmem/dyn/KStructureImpl.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.soywiz.kmem.dyn

import com.soywiz.kmem.*

actual class KArena actual constructor() {
actual fun allocBytes(size: Int): KPointer = KPointer(ByteArray(size))
actual fun clear(): Unit = Unit
Expand All @@ -19,15 +21,15 @@ actual abstract class KStructureBase {
actual fun KPointer(address: Long): KPointer = TODO()
actual val KPointer.address: Long get() = TODO()

actual fun KPointer.getByte(offset: Int): Byte = TODO()
actual fun KPointer.setByte(offset: Int, value: Byte): Unit = TODO()
actual fun KPointer.getShort(offset: Int): Short = TODO()
actual fun KPointer.setShort(offset: Int, value: Short): Unit = TODO()
actual fun KPointer.getInt(offset: Int): Int = TODO()
actual fun KPointer.setInt(offset: Int, value: Int): Unit = TODO()
actual fun KPointer.getFloat(offset: Int): Float = TODO()
actual fun KPointer.setFloat(offset: Int, value: Float): Unit = TODO()
actual fun KPointer.getDouble(offset: Int): Double = TODO()
actual fun KPointer.setDouble(offset: Int, value: Double): Unit = TODO()
actual fun KPointer.getLong(offset: Int): Long = TODO()
actual fun KPointer.setLong(offset: Int, value: Long): Unit = TODO()
actual fun KPointer.getByte(offset: Int): Byte = this.ptr.readS8(offset).toByte()
actual fun KPointer.setByte(offset: Int, value: Byte): Unit = this.ptr.write8(offset, value.toInt())
actual fun KPointer.getShort(offset: Int): Short = this.ptr.readS16LE(offset).toShort()
actual fun KPointer.setShort(offset: Int, value: Short): Unit = this.ptr.write16LE(offset, value.toInt())
actual fun KPointer.getInt(offset: Int): Int = this.ptr.readS32LE(offset)
actual fun KPointer.setInt(offset: Int, value: Int): Unit = this.ptr.write32LE(offset, value)
actual fun KPointer.getFloat(offset: Int): Float = this.ptr.readF32LE(offset)
actual fun KPointer.setFloat(offset: Int, value: Float): Unit = this.ptr.writeF32LE(offset, value)
actual fun KPointer.getDouble(offset: Int): Double = this.ptr.readF64LE(offset)
actual fun KPointer.setDouble(offset: Int, value: Double): Unit = this.ptr.writeF64LE(offset, value)
actual fun KPointer.getLong(offset: Int): Long = this.ptr.readS64LE(offset)
actual fun KPointer.setLong(offset: Int, value: Long): Unit = this.ptr.write64LE(offset, value)
16 changes: 15 additions & 1 deletion kmem/src/commonMain/kotlin/com/soywiz/kmem/dyn/KStructure.kt
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ open class KStructure(pointer: KPointer?) : KStructureBase() {
fun nativeLong(): KMemDelegateNativeLongProperty = layout.nativeLong()
fun kpointer(): KMemDelegateKPointerProperty = layout.kpointer()
fun <T> pointer(): KMemDelegatePointerProperty<T> = layout.pointer<T>()
fun fixedBytes(size: Int): KMemDelegateFixedBytesProperty = layout.fixedBytes(size)

//private var _pointer: KPointer? = pointer; private set
//val pointer: KPointer by lazy {
Expand All @@ -118,7 +119,7 @@ open class KMemLayoutBuilder {
offset
}

private fun alloc(size: Int) = align(size).offset.also { this.offset += size }
private fun alloc(size: Int, align: Int = size) = align(align).offset.also { this.offset += size }

//fun int() = alloc(Int.SIZE_BYTES)
//fun nativeLong() = alloc(NativeLong.SIZE)
Expand All @@ -134,6 +135,7 @@ open class KMemLayoutBuilder {
fun nativeLong() = KMemDelegateNativeLongProperty(alloc(LONG_SIZE))
fun kpointer() = KMemDelegateKPointerProperty(alloc(POINTER_SIZE))
fun <T> pointer() = KMemDelegatePointerProperty<T>(alloc(POINTER_SIZE))
fun fixedBytes(size: Int, align: Int = 1): KMemDelegateFixedBytesProperty = KMemDelegateFixedBytesProperty(alloc(size * Byte.SIZE_BYTES, align), size)
}


Expand All @@ -142,6 +144,18 @@ inline class KMemDelegateByteProperty(val offset: Int) {
operator fun setValue(obj: KStructure, property: KProperty<*>, i: Byte) { obj.pointerSure.setByte(offset, i) }
}

class KMemDelegateFixedBytesProperty(val offset: Int, val size: Int) {
val bytes = ByteArray(size)
operator fun getValue(obj: KStructure, property: KProperty<*>): ByteArray {
for (n in 0 until size) bytes[n] = obj.pointerSure.getByte(offset + n)
return bytes
}
operator fun setValue(obj: KStructure, property: KProperty<*>, i: ByteArray) {
for (n in 0 until size) obj.pointerSure.setByte(offset + n, i[n])
}
}


inline class KMemDelegateBoolProperty(val offset: Int) {
operator fun getValue(obj: KStructure, property: KProperty<*>): Boolean = obj.pointerSure.getInt(offset) != 0
operator fun setValue(obj: KStructure, property: KProperty<*>, i: Boolean) { obj.pointerSure.setInt(offset, if (i) 1 else 0) }
Expand Down
26 changes: 14 additions & 12 deletions kmem/src/jsMain/kotlin/com/soywiz/kmem/dyn/KStructureImpl.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.soywiz.kmem.dyn

import com.soywiz.kmem.*

actual class KArena actual constructor() {
actual fun allocBytes(size: Int): KPointer = KPointer(ByteArray(size))
actual fun clear(): Unit = Unit
Expand All @@ -19,15 +21,15 @@ actual abstract class KStructureBase {
actual fun KPointer(address: Long): KPointer = TODO()
actual val KPointer.address: Long get() = TODO()

actual fun KPointer.getByte(offset: Int): Byte = TODO()
actual fun KPointer.setByte(offset: Int, value: Byte): Unit = TODO()
actual fun KPointer.getShort(offset: Int): Short = TODO()
actual fun KPointer.setShort(offset: Int, value: Short): Unit = TODO()
actual fun KPointer.getInt(offset: Int): Int = TODO()
actual fun KPointer.setInt(offset: Int, value: Int): Unit = TODO()
actual fun KPointer.getFloat(offset: Int): Float = TODO()
actual fun KPointer.setFloat(offset: Int, value: Float): Unit = TODO()
actual fun KPointer.getDouble(offset: Int): Double = TODO()
actual fun KPointer.setDouble(offset: Int, value: Double): Unit = TODO()
actual fun KPointer.getLong(offset: Int): Long = TODO()
actual fun KPointer.setLong(offset: Int, value: Long): Unit = TODO()
actual fun KPointer.getByte(offset: Int): Byte = this.ptr.readS8(offset).toByte()
actual fun KPointer.setByte(offset: Int, value: Byte): Unit = this.ptr.write8(offset, value.toInt())
actual fun KPointer.getShort(offset: Int): Short = this.ptr.readS16LE(offset).toShort()
actual fun KPointer.setShort(offset: Int, value: Short): Unit = this.ptr.write16LE(offset, value.toInt())
actual fun KPointer.getInt(offset: Int): Int = this.ptr.readS32LE(offset)
actual fun KPointer.setInt(offset: Int, value: Int): Unit = this.ptr.write32LE(offset, value)
actual fun KPointer.getFloat(offset: Int): Float = this.ptr.readF32LE(offset)
actual fun KPointer.setFloat(offset: Int, value: Float): Unit = this.ptr.writeF32LE(offset, value)
actual fun KPointer.getDouble(offset: Int): Double = this.ptr.readF64LE(offset)
actual fun KPointer.setDouble(offset: Int, value: Double): Unit = this.ptr.writeF64LE(offset, value)
actual fun KPointer.getLong(offset: Int): Long = this.ptr.readS64LE(offset)
actual fun KPointer.setLong(offset: Int, value: Long): Unit = this.ptr.write64LE(offset, value)
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import com.sun.jna.Pointer

actual class KArena actual constructor() {
private val pointers = arrayListOf<Memory>()
actual fun allocBytes(size: Int): KPointer = KPointer(Memory(size.toLong()).also { pointers += it })
actual fun allocBytes(size: Int): KPointer = KPointer(Memory(size.toLong()).also {
it.clear()
pointers += it
})
actual fun clear() {
for (n in 0 until pointers.size) pointers[n].clear()
pointers.clear()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import platform.posix.*

actual class KArena actual constructor() {
private val arena = Arena()
actual fun allocBytes(size: Int): KPointer = arena.allocArray<ByteVar>(size)
actual fun allocBytes(size: Int): KPointer {
return arena.allocArray<ByteVar>(size).also {
memset(it, 0, size.convert())
}
}
actual fun clear(): Unit = arena.clear()
}

Expand Down
25 changes: 14 additions & 11 deletions korgw/src/commonMain/kotlin/com/soywiz/korev/InputGamepad.kt
Original file line number Diff line number Diff line change
Expand Up @@ -223,20 +223,23 @@ class GamepadInfo(
GameStick.LEFT -> get(GameButton.LY)
GameStick.RIGHT -> get(GameButton.RY)
}
override fun toString(): String = buildString {
fun toStringEx(includeButtons: Boolean = true): String = buildString {
append("Gamepad[$index][$fullName]")
append("[")
var count = 0
for (button in GameButton.values()) {
val value = this@GamepadInfo[button]
if (value != 0.0) {
if (count > 0) append(",")
append("$button=${value.niceStr}")
count++
if (includeButtons) {
append("[")
var count = 0
for (button in GameButton.values()) {
val value = this@GamepadInfo[button]
if (value != 0.0) {
if (count > 0) append(",")
append("$button=${value.niceStr}")
count++
}
}
append("]")
}
append("]")
}
override fun toString(): String = toStringEx(includeButtons = false)
}

data class GamePadConnectionEvent(
Expand Down Expand Up @@ -272,5 +275,5 @@ data class GamePadUpdateEvent @JvmOverloads constructor(
}
}

override fun toString(): String = "GamePadUpdateEvent(${gamepads.filter { it.connected }})"
override fun toString(): String = "GamePadUpdateEvent(${gamepads.filter { it.connected }.map { it.toStringEx() }})"
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package com.soywiz.korgw.win32

import com.soywiz.kmem.*
import com.soywiz.kmem.dyn.*
import com.soywiz.korev.*
import com.soywiz.korio.lang.*

internal class Win32XInputEventAdapterCommon(val xinput: XInput?, val joy: Joy32?) {
private val controllers = Array(GamepadInfo.MAX_CONTROLLERS) { GamepadInfo(it) }

fun updateGamepads(emitter: GamepadInfoEmitter) {
if (xinput == null) return

emitter.dispatchGamepadUpdateStart()
kmemScoped {
val state = XInputState(allocBytes(XInputState().size))
for (n in 0 until GamepadInfo.MAX_CONTROLLERS) {
val connected = xinput.XInputGetState(n, state) == XInput.SUCCESS
val gamepad = controllers[n]
if (connected) {
val buttons = state.wButtons.toInt() and 0xFFFF

gamepad.setDigital(GameButton.UP, buttons, XInput.GAMEPAD_DPAD_UP)
gamepad.setDigital(GameButton.DOWN, buttons, XInput.GAMEPAD_DPAD_DOWN)
gamepad.setDigital(GameButton.LEFT, buttons, XInput.GAMEPAD_DPAD_LEFT)
gamepad.setDigital(GameButton.RIGHT, buttons, XInput.GAMEPAD_DPAD_RIGHT)
gamepad.setDigital(GameButton.BACK, buttons, XInput.GAMEPAD_BACK)
gamepad.setDigital(GameButton.START, buttons, XInput.GAMEPAD_START)
gamepad.setDigital(GameButton.LEFT_THUMB, buttons, XInput.GAMEPAD_LEFT_THUMB)
gamepad.setDigital(GameButton.RIGHT_THUMB, buttons, XInput.GAMEPAD_RIGHT_THUMB)
gamepad.setDigital(GameButton.LEFT_SHOULDER, buttons, XInput.GAMEPAD_LEFT_SHOULDER)
gamepad.setDigital(GameButton.RIGHT_SHOULDER, buttons, XInput.GAMEPAD_RIGHT_SHOULDER)
gamepad.setDigital(GameButton.XBOX_A, buttons, XInput.GAMEPAD_A)
gamepad.setDigital(GameButton.XBOX_B, buttons, XInput.GAMEPAD_B)
gamepad.setDigital(GameButton.XBOX_X, buttons, XInput.GAMEPAD_X)
gamepad.setDigital(GameButton.XBOX_Y, buttons, XInput.GAMEPAD_Y)
gamepad.rawButtons[GameButton.LEFT_TRIGGER.index] = convertUByteRangeToDouble(state.bLeftTrigger)
gamepad.rawButtons[GameButton.RIGHT_TRIGGER.index] = convertUByteRangeToDouble(state.bRightTrigger)
gamepad.rawButtons[GameButton.LX.index] = GamepadInfo.withoutDeadRange(convertShortRangeToDouble(state.sThumbLX))
gamepad.rawButtons[GameButton.LY.index] = GamepadInfo.withoutDeadRange(convertShortRangeToDouble(state.sThumbLY))
gamepad.rawButtons[GameButton.RX.index] = GamepadInfo.withoutDeadRange(convertShortRangeToDouble(state.sThumbRX))
gamepad.rawButtons[GameButton.RY.index] = GamepadInfo.withoutDeadRange(convertShortRangeToDouble(state.sThumbRY))

if (gamepad.name == null) {
kmemScoped {
val joyCapsW = JoyCapsW(allocBytes(JoyCapsW.SIZE))
if (joy?.joyGetDevCapsW(n, joyCapsW, JoyCapsW.SIZE) == 0) {
gamepad.name = joyCapsW.name
}
}
}
emitter.dispatchGamepadUpdateAdd(gamepad)
}
}
emitter.dispatchGamepadUpdateEnd()
}
}

private fun GamepadInfo.setDigital(button: GameButton, buttons: Int, bit: Int) {
this.rawButtons[button.index] = if (buttons.hasBitSet(bit)) 1f else 0f
}

private fun convertShortRangeToDouble(value: Short): Float = value.toFloat().convertRangeClamped(Short.MIN_VALUE.toFloat(), Short.MAX_VALUE.toFloat(), -1f, +1f)
private fun convertUByteRangeToDouble(value: Byte): Float = (value.toInt() and 0xFF).toFloat().convertRangeClamped(0f, 255f, 0f, +1f)
}

// Used this as reference:
// https://github.com/fantarama/JXInput/blob/86356e7a4037bbb1f3478c7333555e00b3601bde/XInputJNA/src/main/java/com/microsoft/xinput/XInput.java
internal class XInputState(pointer: KPointer? = null) : KStructure(pointer) {
var dwPacketNumber by int() // offset: 0
var wButtons by short() // offset: 4
var bLeftTrigger by byte() // offset: 6
var bRightTrigger by byte() // offset: 7
var sThumbLX by short() // offset: 8
var sThumbLY by short() // offset: 10
var sThumbRX by short() // offset: 12
var sThumbRY by short() // offset: 14
override fun toString(): String =
"XInputState(dwPacketNumber=$dwPacketNumber, wButtons=$wButtons, bLeftTrigger=$bLeftTrigger, bRightTrigger=$bRightTrigger, sThumbLX=$sThumbLX, sThumbLY=$sThumbLY, sThumbRX=$sThumbRX, sThumbRY=$sThumbRY)"
}

internal class JoyCapsW(pointer: KPointer? = null) : KStructure(pointer) {
companion object {
val SIZE = 728
}

var wMid: Short by short()
var wPid: Short by short()
var szPname by fixedBytes(32 * 2)
var name: String
get() = szPname.toString(Charsets.UTF16_LE).trimEnd('\u0000').also {
//println("JoyCapsW.name='$it'")
}
set(value) {
szPname = run {
ByteArray(szPname.size).also {
val new = value.toByteArray(Charsets.UTF16_LE)
arraycopy(new, 0, it, 0, new.size)
}
}

}
override fun toString(): String =
"JoyCapsW(name=$name)"
}

internal fun interface XInput {
fun XInputGetState(dwUserIndex: Int, pState: XInputState): Int // pState: XInputState

companion object {
const val SUCCESS = 0
const val ERROR_DEVICE_NOT_CONNECTED = 0x0000048F

const val GAMEPAD_DPAD_UP = 0
const val GAMEPAD_DPAD_DOWN = 1
const val GAMEPAD_DPAD_LEFT = 2
const val GAMEPAD_DPAD_RIGHT = 3
const val GAMEPAD_START = 4
const val GAMEPAD_BACK = 5
const val GAMEPAD_LEFT_THUMB = 6
const val GAMEPAD_RIGHT_THUMB = 7
const val GAMEPAD_LEFT_SHOULDER = 8
const val GAMEPAD_RIGHT_SHOULDER = 9
const val GAMEPAD_UNKNOWN_10 = 10
const val GAMEPAD_UNKNOWN_11 = 11
const val GAMEPAD_A = 12
const val GAMEPAD_B = 13
const val GAMEPAD_X = 14
const val GAMEPAD_Y = 15

const val SIZE = 16
}
}

internal fun interface Joy32 {
fun joyGetDevCapsW(uJoyID: Int, pjc: JoyCapsW, cbjc: Int): Int // pjc: JoyCapsW
}
Loading