changeset 1022:f888ac4825b8

did: refactor input system, did some renaming, add quering of keys in key-value-store
author sam <sam@basx.dev>
date Wed, 15 May 2024 19:51:23 +0700
parents 73b572f82a1f
children 73db28170930
files semicongine/core/buildconfig.nim semicongine/engine.nim semicongine/input.nim semicongine/storage.nim
diffstat 4 files changed, 157 insertions(+), 79 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine/core/buildconfig.nim	Thu May 09 23:02:35 2024 +0700
+++ b/semicongine/core/buildconfig.nim	Wed May 15 19:51:23 2024 +0700
@@ -29,7 +29,7 @@
 
 # root of where settings files will be searched
 # must be relative (to the directory of the binary)
-const DEBUG* = not defined(release)
+const DEBUG* {.booldefine.} = not defined(release)
 const CONFIGROOT* {.strdefine.}: string = "."
 assert not isAbsolute(CONFIGROOT)
 
--- a/semicongine/engine.nim	Thu May 09 23:02:35 2024 +0700
+++ b/semicongine/engine.nim	Wed May 15 19:51:23 2024 +0700
@@ -34,7 +34,6 @@
     Shutdown
   Engine* = object
     applicationName: string
-    debug: bool
     showFps: bool
     device: Device
     debugger: Debugger
@@ -46,7 +45,7 @@
     currentRenderTimeI: int = 0
 
 # forward declarations
-func getAspectRatio*(engine: Engine): float32
+func GetAspectRatio*(engine: Engine): float32
 
 proc destroy*(engine: var Engine) =
   checkVkResult engine.device.vk.vkDeviceWaitIdle()
@@ -63,7 +62,6 @@
 
 proc initEngine*(
   applicationName = querySetting(projectName),
-  debug = DEBUG,
   showFps = DEBUG,
   vulkanVersion = VK_MAKE_API_VERSION(0, 1, 3, 0),
   vulkanLayers: openArray[string] = [],
@@ -78,7 +76,6 @@
     echo "Starting without Steam"
 
   result.applicationName = applicationName
-  result.debug = debug
   result.showFps = showFps
   result.window = createWindow(result.applicationName)
 
@@ -86,7 +83,7 @@
     layers = @vulkanLayers
     instanceExtensions: seq[string]
 
-  if result.debug:
+  if DEBUG:
     instanceExtensions.add "VK_EXT_debug_utils"
     layers.add "VK_LAYER_KHRONOS_validation"
     # This stuff might be usefull if we one day to smart GPU memory allocation,
@@ -99,7 +96,7 @@
     instanceExtensions = instanceExtensions,
     layers = layers.deduplicate(),
   )
-  if result.debug:
+  if DEBUG:
     result.debugger = result.instance.createDebugMessenger()
   # create devices
   let selectedPhysicalDevice = result.instance.getPhysicalDevices().filterBestGraphics()
@@ -145,7 +142,7 @@
   assert engine.renderer.isSome
   assert not scene.loaded
   checkVkResult engine.device.vk.vkDeviceWaitIdle()
-  scene.addShaderGlobal(ASPECT_RATIO_ATTRIBUTE, engine.getAspectRatio)
+  scene.addShaderGlobal(ASPECT_RATIO_ATTRIBUTE, engine.GetAspectRatio)
   engine.renderer.get.setupDrawableBuffers(scene)
   engine.renderer.get.updateMeshData(scene, forceAll = true)
   engine.renderer.get.updateUniformData(scene, forceAll = true)
@@ -162,7 +159,7 @@
   let t0 = getMonoTime()
 
   engine.renderer.get.startNewFrame()
-  scene.setShaderGlobal(ASPECT_RATIO_ATTRIBUTE, engine.getAspectRatio)
+  scene.setShaderGlobal(ASPECT_RATIO_ATTRIBUTE, engine.GetAspectRatio)
   engine.renderer.get.updateMeshData(scene)
   engine.renderer.get.updateUniformData(scene)
   engine.renderer.get.render(scene)
@@ -182,25 +179,25 @@
 
 
 # wrappers for internal things
-func gpuDevice*(engine: Engine): Device = engine.device
-func getWindow*(engine: Engine): auto = engine.window
-func getAspectRatio*(engine: Engine): float32 = engine.getWindow().size[0] / engine.getWindow().size[1]
-func showSystemCursor*(engine: Engine) = engine.window.showSystemCursor()
-func hideSystemCursor*(engine: Engine) = engine.window.hideSystemCursor()
-func fullscreen*(engine: Engine): bool = engine.fullscreen
-proc `fullscreen=`*(engine: var Engine, enable: bool) =
+func GpuDevice*(engine: Engine): Device = engine.device
+func GetWindow*(engine: Engine): auto = engine.window
+func GetAspectRatio*(engine: Engine): float32 = engine.GetWindow().size[0] / engine.GetWindow().size[1]
+func ShowSystemCursor*(engine: Engine) = engine.window.showSystemCursor()
+func HideSystemCursor*(engine: Engine) = engine.window.hideSystemCursor()
+func Fullscreen*(engine: Engine): bool = engine.fullscreen
+proc `Fullscreen=`*(engine: var Engine, enable: bool) =
   if enable != engine.fullscreen:
     engine.fullscreen = enable
     engine.window.fullscreen(engine.fullscreen)
 
-func limits*(engine: Engine): VkPhysicalDeviceLimits =
-  engine.gpuDevice().physicalDevice.properties.limits
+func Limits*(engine: Engine): VkPhysicalDeviceLimits =
+  engine.device.physicalDevice.properties.limits
 
-proc updateInputs*(engine: Engine): bool =
-  updateInputs(engine.window.pendingEvents())
+proc UpdateInputs*(engine: Engine): bool =
+  UpdateInputs(engine.window.pendingEvents())
 
-proc processEvents*(engine: Engine, panel: var Panel) =
-  let hasMouseNow = panel.contains(mousePositionNormalized(engine.window.size), engine.getAspectRatio)
+proc ProcessEvents*(engine: Engine, panel: var Panel) =
+  let hasMouseNow = panel.contains(MousePositionNormalized(engine.window.size), engine.GetAspectRatio)
 
   # enter/leave events
   if hasMouseNow:
@@ -214,9 +211,9 @@
 
   # button events
   if hasMouseNow:
-    if input.mouseWasPressed():
-      if panel.onMouseDown != nil: panel.onMouseDown(panel, input.mousePressedButtons())
-    if input.mouseWasReleased():
-      if panel.onMouseUp != nil: panel.onMouseUp(panel, input.mouseReleasedButtons())
+    if MouseWasPressed():
+      if panel.onMouseDown != nil: panel.onMouseDown(panel, MousePressedButtons())
+    if MouseWasReleased():
+      if panel.onMouseUp != nil: panel.onMouseUp(panel, MouseReleasedButtons())
 
   panel.hasMouse = hasMouseNow
--- a/semicongine/input.nim	Thu May 09 23:02:35 2024 +0700
+++ b/semicongine/input.nim	Wed May 15 19:51:23 2024 +0700
@@ -1,33 +1,13 @@
 # Linux joystick: https://www.kernel.org/doc/Documentation/input/joystick-api.txt
 # Windows joystick: https://learn.microsoft.com/en-us/windows/win32/xinput/getting-started-with-xinput
-#
-# API to define actions that are connected to user inputs
-#
-# Example:
-#
-# type
-#   Action = enum
-#     Jump
-#     Left
-#     Right
-#
-# AddAction(Jump, SpaceDown, Pressed) # trigger action
-# AddAction(Left, Arrow, Down) # boolean action
-# AddAction(Left, Joystick_Left, Axis) # axis action
-#
-#
-#
-#
-# if Action(Jump).Triggered:
-#   accel_y = 1
-# if Action(Left).Active:
-#   accel_y = 1
-# if Action(Left).Value:
-#   accel_y = 1
+
 
+import std/tables
+import std/strutils
 
 import ./core/vector
 import ./events
+import ./storage
 
 type
   Input = object
@@ -42,9 +22,10 @@
     mouseWheel: float32
     windowWasResized: bool = true
 
-var input*: Input
+# warning, shit is not thread safe
+var input: Input
 
-proc updateInputs*(events: seq[Event]): bool =
+proc UpdateInputs*(events: seq[Event]): bool =
   # reset input states
   input.keyWasPressed = {}
   input.keyWasReleased = {}
@@ -81,21 +62,115 @@
         input.mouseWheel = event.amount
   return not killed
 
-proc keyIsDown*(key: Key): bool = key in input.keyIsDown
-proc keyWasPressed*(key: Key): bool = key in input.keyWasPressed
-proc keyWasPressed*(): bool = input.keyWasPressed.len > 0
-proc keyWasReleased*(key: Key): bool = key in input.keyWasReleased
-proc mouseIsDown*(button: MouseButton): bool = button in input.mouseIsDown
-proc mouseWasPressed*(): bool = input.mouseWasPressed.len > 0
-proc mouseWasPressed*(button: MouseButton): bool = button in input.mouseWasPressed
-proc mousePressedButtons*(): set[MouseButton] = input.mouseWasPressed
-proc mouseWasReleased*(): bool = input.mouseWasReleased.len > 0
-proc mouseWasReleased*(button: MouseButton): bool = button in input.mouseWasReleased
-proc mouseReleasedButtons*(): set[MouseButton] = input.mouseWasReleased
-proc mousePosition*(): Vec2f = input.mousePosition
-proc mousePositionNormalized*(size: (int, int)): Vec2f =
+proc KeyIsDown*(key: Key): bool = key in input.keyIsDown
+proc KeyWasPressed*(key: Key): bool = key in input.keyWasPressed
+proc KeyWasPressed*(): bool = input.keyWasPressed.len > 0
+proc KeyWasReleased*(key: Key): bool = key in input.keyWasReleased
+proc MouseIsDown*(button: MouseButton): bool = button in input.mouseIsDown
+proc MouseWasPressed*(): bool = input.mouseWasPressed.len > 0
+proc MouseWasPressed*(button: MouseButton): bool = button in input.mouseWasPressed
+proc MousePressedButtons*(): set[MouseButton] = input.mouseWasPressed
+proc MouseWasReleased*(): bool = input.mouseWasReleased.len > 0
+proc MouseWasReleased*(button: MouseButton): bool = button in input.mouseWasReleased
+proc MouseReleasedButtons*(): set[MouseButton] = input.mouseWasReleased
+proc MousePosition*(): Vec2f = input.mousePosition
+proc MousePositionNormalized*(size: (int, int)): Vec2f =
   result.x = (input.mousePosition.x / float32(size[0])) * 2.0 - 1.0
   result.y = (input.mousePosition.y / float32(size[1])) * 2.0 - 1.0
-proc mouseMove*(): auto = input.mouseMove
-proc mouseWheel*(): auto = input.mouseWheel
-proc windowWasResized*(): auto = input.windowWasResized
+proc MouseMove*(): auto = input.mouseMove
+proc MouseWheel*(): auto = input.mouseWheel
+proc WindowWasResized*(): auto = input.windowWasResized
+
+# actions as a slight abstraction over raw input
+
+type
+  ActionMap = object
+    keyActions: Table[string, set[Key]]
+    mouseActions: Table[string, set[MouseButton]]
+
+# warning, shit is not thread safe
+var actionMap: ActionMap
+
+proc MapAction*[T: enum](action: T, key: Key) =
+  if not actionMap.keyActions.contains($action):
+    actionMap.keyActions[$action] = {}
+  actionMap.keyActions[$action].incl key
+
+proc MapAction*[T: enum](action: T, button: MouseButton) =
+  if not actionMap.mouseActions.contains($action):
+    actionMap.mouseActions[$action] = {}
+  actionMap.mouseActions[$action].incl button
+
+proc MapAction*[T: enum](action: T, keys: openArray[Key|MouseButton]) =
+  for key in keys:
+    MapAction(action, key)
+
+proc UnmapAction*[T: enum](action: T, key: Key) =
+  if actionMap.keyActions.contains($action):
+    actionMap.keyActions[$action].excl(key)
+
+proc UnmapAction*[T: enum](action: T, button: MouseButton) =
+  if actionMap.mouseActions.contains($action):
+    actionMap.mouseActions[$action].excl(button)
+
+proc UnmapAction*[T: enum](action: T) =
+  if actionMap.keyActions.contains($action):
+    actionMap.keyActions[$action] = {}
+  if actionMap.mouseActions.contains($action):
+    actionMap.mouseActions[$action] = {}
+
+proc SaveCurrentActionMapping*() =
+  for name, keys in actionMap.keyActions.pairs:
+    SystemStorage.store(name, keys, table = "input_mapping_key")
+  for name, buttons in actionMap.mouseActions.pairs:
+    SystemStorage.store(name, buttons, table = "input_mapping_mouse")
+
+proc LoadActionMapping*[T]() =
+  reset(actionMap)
+  for name in SystemStorage.list(table = "input_mapping_key"):
+    let action = parseEnum[T](name)
+    let keys = SystemStorage.load(name, set[Key](), table = "input_mapping_key")
+    for key in keys:
+      MapAction(action, key)
+
+proc ActionDown*[T](action: T): bool =
+  if actionMap.keyActions.contains($action):
+    for key in actionMap.keyActions[$action]:
+      if key in input.keyIsDown:
+        return true
+    return false
+  if actionMap.mouseActions.contains($action):
+    for button in actionMap.mouseActions[$action]:
+      if button in input.mouseIsDown:
+        return true
+    return false
+
+proc ActionPressed*[T](action: T): bool =
+  if actionMap.keyActions.contains($action):
+    for key in actionMap.keyActions[$action]:
+      if key in input.keyWasPressed:
+        return true
+  elif actionMap.mouseActions.contains($action):
+    for button in actionMap.mouseActions[$action]:
+      if button in input.mouseWasPressed:
+        return true
+
+proc ActionReleased*[T](action: T): bool =
+  if actionMap.keyActions.contains($action):
+    for key in actionMap.keyActions[$action]:
+      if key in input.keyWasReleased:
+        return true
+  elif actionMap.mouseActions.contains($action):
+    for button in actionMap.mouseActions[$action]:
+      if button in input.mouseWasReleased:
+        return true
+
+proc ActionValue*[T](action: T): float32 =
+  if actionMap.keyActions.contains($action):
+    for key in actionMap.keyActions[$action]:
+      if key in input.keyIsDown:
+        return 1
+  elif actionMap.mouseActions.contains($action):
+    for button in actionMap.mouseActions[$action]:
+      if button in input.mouseIsDown:
+        return 1
--- a/semicongine/storage.nim	Thu May 09 23:02:35 2024 +0700
+++ b/semicongine/storage.nim	Wed May 15 19:51:23 2024 +0700
@@ -9,7 +9,7 @@
 import ./core
 
 const STORAGE_NAME = Path("storage.db")
-const KEY_VALUE_TABLE_NAME = "shelf"
+const DEFAULT_KEY_VALUE_TABLE_NAME = "shelf"
 
 type
   StorageType* = enum
@@ -27,29 +27,35 @@
       string(Path(getDataDir()) / Path(AppName())).createDir()
       Path(getDataDir()) / Path(AppName()) / STORAGE_NAME
 
-proc setup(storageType: StorageType) =
+proc ensureExists(storageType: StorageType) =
   if storageType in db:
     return
   db[storageType] = open(string(storageType.path), "", "", "")
-  db[storageType].exec(sql(&"""CREATE TABLE IF NOT EXISTS {KEY_VALUE_TABLE_NAME} (
+
+proc ensureExists(storageType: StorageType, table: string) =
+  storageType.ensureExists()
+  db[storageType].exec(sql(&"""CREATE TABLE IF NOT EXISTS {table} (
     key TEXT NOT NULL UNIQUE,
     value TEXT NOT NULL
   )"""))
 
-proc store*[T](storageType: StorageType, key: string, value: T) =
-  storageType.setup()
-  const KEY_VALUE_TABLE_NAME = "shelf"
-  db[storageType].exec(sql(&"""INSERT INTO {KEY_VALUE_TABLE_NAME} VALUES(?, ?)
+proc store*[T](storageType: StorageType, key: string, value: T, table = DEFAULT_KEY_VALUE_TABLE_NAME) =
+  storageType.ensureExists(table)
+  db[storageType].exec(sql(&"""INSERT INTO {table} VALUES(?, ?)
   ON CONFLICT(key) DO UPDATE SET value=excluded.value
   """), key, $$value)
 
-proc load*[T](storageType: StorageType, key: string, default: T): T =
-  storageType.setup()
-  const KEY_VALUE_TABLE_NAME = "shelf"
-  let dbResult = db[storageType].getValue(sql(&"""SELECT value FROM {KEY_VALUE_TABLE_NAME} WHERE key = ? """), key)
+proc load*[T](storageType: StorageType, key: string, default: T, table = DEFAULT_KEY_VALUE_TABLE_NAME): T =
+  storageType.ensureExists(table)
+  let dbResult = db[storageType].getValue(sql(&"""SELECT value FROM {table} WHERE key = ? """), key)
   if dbResult == "":
     return default
   return to[T](dbResult)
 
+proc list*[T](storageType: StorageType, table = DEFAULT_KEY_VALUE_TABLE_NAME): seq[string] =
+  storageType.ensureExists(table)
+  for row in db[storageType].fastRows(sql(&"""SELECT key FROM {table}""")):
+    result.add row[0]
+
 proc purge*(storageType: StorageType) =
   storageType.path().string.removeFile()