changeset 674:496b328faa43

add: resource packaging and loading for different resource types, simplify build commands, update readme
author Sam <sam@basx.dev>
date Wed, 10 May 2023 22:36:59 +0700
parents 9cfd59c93ae8
children cba4bebaf9d0
files README.md config.nims semicongine.nimble src/semicongine/core/buildconfig.nim src/semicongine/resources.nim tests/test_resources.nim
diffstat 6 files changed, 152 insertions(+), 131 deletions(-) [+]
line wrap: on
line diff
--- a/README.md	Tue May 09 20:39:49 2023 +0700
+++ b/README.md	Wed May 10 22:36:59 2023 +0700
@@ -3,8 +3,9 @@
 
 Hi there
 
-This is a very simplistic little game engine, mainly trying to wrap around vulkan and the operating system's windowing, input and audio system.
-I am using the last programming language you will ever need, [Nim](https://nim-lang.org/)
+This is a little game engine, mainly trying to wrap around vulkan and the
+operating system's windowing, input and audio system. I am using the last
+programming language you will ever need, [Nim](https://nim-lang.org/)
 
 Building
 --------
@@ -42,7 +43,11 @@
 - [ ] Mipmaps 
 
 Asset handling:
-- [ ] Resource concept: load from directory, zip or in-memory-zip, select "mod" as root
+- [ ] Resource concept (TODO: test on windows)
+      - [x] Mod/resource-pack concept
+      - [x] Load from directory
+      - [x] Load from zip
+      - [x] Load from exe-embeded
 - [ ] Mesh files (Wavefront OBJ, MTL) (use something from sketchfab for testing, https://sketchfab.com/)
 - [ ] Image files (BMP RGB + BMP Graysscale for transparency)
 - [ ] Audio files (AU)
--- a/config.nims	Tue May 09 20:39:49 2023 +0700
+++ b/config.nims	Wed May 10 22:36:59 2023 +0700
@@ -8,91 +8,62 @@
 const LINUX = "linux"
 const WINDOWS = "windows"
 
-proc compilerFlags() =
+const BUNDLETYPE* {.strdefine.}: string = "dir" # dir, zip, exe
+const RESOURCEROOT* {.strdefine.}: string = "resources"
+
+task build, "build":
   switch("path", "src")
   switch("mm", "orc")
   switch("experimental", "strictEffects")
   switch("threads", "on")
+  var buildType = DEBUG
+  var platformDir = ""
   if defined(linux):
     switch("define", "VK_USE_PLATFORM_XLIB_KHR")
+    platformDir = LINUX
   if defined(windows):
     switch("define", "VK_USE_PLATFORM_WIN32_KHR")
-
-
-proc compilerFlagsDebug() =
-  switch("debugger", "native")
-
-proc compilerFlagsRelease() =
-  switch("define", "release")
-  switch("app", "gui")
-
-task single_linux_debug, "build linux debug":
-  compilerFlags()
-  compilerFlagsDebug()
-  switch("outdir", BUILDBASE / DEBUG / LINUX)
-  setCommand "c"
-  mkDir(BUILDBASE / DEBUG / LINUX)
+    platformDir = WINDOWS
+  if defined(release):
+    switch("app", "gui")
+    buildType = RELEASE
+  else:
+    switch("debugger", "native")
 
-task single_linux_release, "build linux release":
-  compilerFlags()
-  compilerFlagsRelease()
-  switch("outdir", BUILDBASE / RELEASE / LINUX)
+  var outdir = getCurrentDir() / BUILDBASE / buildType / platformDir / projectName()
+  switch("outdir", outdir)
   setCommand "c"
-  mkDir(BUILDBASE / RELEASE / LINUX)
+  rmDir(outdir)
+  mkDir(outdir)
+  let resourcedir = joinPath(projectDir(), RESOURCEROOT)
+  if existsDir(resourcedir):
+    let outdir_resources = joinPath(outdir, RESOURCEROOT)
+    if BUNDLETYPE == "dir":
+      cpDir(resourcedir, outdir_resources)
+    elif BUNDLETYPE == "zip":
+      mkDir(outdir_resources)
+      for resource in listDirs(resourcedir):
+        let
+          oldcwd = getCurrentDir()
+          outputfile = joinPath(outdir_resources, resource.splitPath().tail & ".zip")
+          inputfile = resource.splitPath().tail
+        cd(resource)
+        if defined(linux):
+          exec &"zip -r {outputfile} ."
+        elif defined(windows):
+          # TODO: test this
+          exec &"powershell Compress-Archive {inputfile} {outputfile}"
+        cd(oldcwd)
 
-task single_windows_debug, "build windows debug":
-  compilerFlags()
-  compilerFlagsDebug()
-  switch("outdir", BUILDBASE & "/" & DEBUG & "/" & WINDOWS)
-  setCommand "c"
-  mkDir(BUILDBASE & "/" & DEBUG & "/" & WINDOWS)
-
-task single_windows_release, "build windows release":
-  compilerFlags()
-  compilerFlagsRelease()
-  switch("outdir", BUILDBASE & "/" & RELEASE & "/" & WINDOWS)
-  setCommand "c"
-  mkDir(BUILDBASE & "/" & RELEASE & "/" & WINDOWS)
-
-task build_all_linux_debug, "build all examples with linux/debug":
+task build_all_debug, "build all examples for debug":
   for file in listFiles("examples"):
     if file.endsWith(".nim"):
-      selfExec(&"single_linux_debug {file}")
-
-task build_all_linux_release, "build all examples with linux/release":
-  for file in listFiles("examples"):
-    if file.endsWith(".nim"):
-      selfExec(&"single_linux_release {file}")
+      exec(&"nim build {file}")
 
-task build_all_windows_debug, "build all examples with windows/debug":
-  for file in listFiles("examples"):
-    if file.endsWith(".nim"):
-      exec(&"nim single_windows_debug --define:mingw {file}")
-
-task build_all_windows_release, "build all examples with windows/release":
+task build_all_release, "build all examples for release":
   for file in listFiles("examples"):
     if file.endsWith(".nim"):
-      exec(&"nim single_windows_release --define:mingw {file}")
-
-task build_all_debug, "build all examples with */debug":
-  build_all_linux_debugTask()
-  build_all_windows_debugTask()
-
-task build_all_release, "build all examples with */release":
-  build_all_linux_releaseTask()
-  build_all_windows_releaseTask()
-
-task build_all_linux, "build all examples with linux/*":
-  build_all_linux_debugTask()
-  build_all_linux_releaseTask()
-
-task build_all_windows, "build all examples with windows/*":
-  build_all_windows_debugTask()
-  build_all_windows_releaseTask()
-
-task build_all, "build all examples":
-  build_all_linuxTask()
-  build_all_windowsTask()
+      exec(&"nim build -d:release {file}")
 
 task clean, "remove all build files":
   exec(&"rm -rf {BUILDBASE}")
@@ -116,6 +87,7 @@
   exec &"rm -rf {dirname}"
 
 task glslangValidator_exe, "Download glslangValidator.exe (required for windows compilation)":
+  # TODO: make this work on windows
   let dirname = "/tmp/glslang_download"
   exec &"mkdir -p {dirname}"
   exec &"cd {dirname} && wget https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-windows-x64-Release.zip"
@@ -144,4 +116,10 @@
   cpDir "src/vulkan_api/output/platform", "src/semicongine/vulkan/platform"
 
 if getCommand() in ["c", "compile", "r", "dump", "check", "idetools"]:
-  compilerFlags()
+  --path:src
+  --mm:orc
+  --threads:on
+  if defined(linux):
+    --d:VK_USE_PLATFORM_XLIB_KHR
+  if defined(windows):
+    --d:VK_USE_PLATFORM_WIN32_KHR
--- a/semicongine.nimble	Tue May 09 20:39:49 2023 +0700
+++ b/semicongine.nimble	Wed May 10 22:36:59 2023 +0700
@@ -12,3 +12,4 @@
 requires "nim >= 1.6.10"
 requires "winim"
 requires "xlib"
+requires "zippy"
--- a/src/semicongine/core/buildconfig.nim	Tue May 09 20:39:49 2023 +0700
+++ b/src/semicongine/core/buildconfig.nim	Wed May 10 22:36:59 2023 +0700
@@ -52,5 +52,5 @@
 const LOGLEVEL {.strdefine.}: string = (when DEBUG: "lvlAll" else: "lvlWarn")
 const ENGINE_LOGLEVEL* = parseEnum[Level](LOGLEVEL)
 
-const MODROOT* {.strdefine.}: string = "mods"
-const BUNDLETYPE* {.strdefine.}: string = "Dir" # zip, exe
+const RESOURCEROOT* {.strdefine.}: string = "resources"
+const BUNDLETYPE* {.strdefine.}: string = "dir" # dir, zip, exe
--- a/src/semicongine/resources.nim	Tue May 09 20:39:49 2023 +0700
+++ b/src/semicongine/resources.nim	Wed May 10 22:36:59 2023 +0700
@@ -4,42 +4,97 @@
 import ./core
 
 type
-  ResourceBundlingType* = enum
+  Binary* = seq[uint8]
+  ResourceBundlingType = enum
     Dir # Directories
     Zip # Zip files
     Exe # Embeded in executable
-  Resource = ref object of RootObj
-    name: string
-    data: seq[uint8]
+
+const thebundletype = parseEnum[ResourceBundlingType](BUNDLETYPE.toLowerAscii().capitalizeAscii())
+var selectedMod* = "default"
+
+# resource loading
+
+when thebundletype == Dir:
+
+  proc resourceRoot(): string =
+    joinPath(absolutePath(getAppDir()), RESOURCEROOT)
+  proc modRoot(): string =
+    joinPath(resourceRoot(), selectedMod)
+
+  proc loadResource_intern(path: string): Binary =
+    cast[Binary](readFile(joinPath(modRoot(), path)))
+
+  proc modList_intern(): seq[string] =
+    for kind, file in walkDir(resourceRoot(), relative=true):
+      if kind == pcDir:
+        result.add file
 
-const thebundletype = parseEnum[ResourceBundlingType](BUNDLETYPE)
+  iterator walkResources_intern(): string =
+    for i in walkDir(modRoot(), relative=true):
+      yield i.path
+
+elif thebundletype == Zip:
+
+  import zippy/ziparchives
+
+  proc resourceRoot(): string =
+    joinPath(absolutePath(getAppDir()), RESOURCEROOT)
+  proc modRoot(): string =
+    joinPath(resourceRoot(), selectedMod)
+
+  proc loadResource_intern(path: string): Binary =
+    let reader = openZipArchive(modRoot() & ".zip")
+    result = cast[Binary](reader.extractFile(path))
+    reader.close()
+
+  proc modList_intern(): seq[string] =
+    for kind, file in walkDir(resourceRoot(), relative=true):
+      if kind == pcFile and file.endsWith(".zip"):
+        result.add file[0 ..< ^4]
 
-proc loadResourceDirectory(path: string): Resource =
-  var fullpath = joinPath(MODROOT, path)
-  result
-proc loadResourceZip(path: string): Resource =
-  var fullpath = joinPath(MODROOT, path)
-  result
-proc loadResourceExecutable(path: string): Resource =
-  result
-proc loadResource*(path: string): Resource =
-  case thebundletype
-  of Dir: return loadResourceDirectory(path)
-  of Zip: return loadResourceZip(path)
-  of Exe: return loadResourceExecutable(path)
+  iterator walkResources_intern(): string =
+    let reader = openZipArchive(modRoot() & ".zip")
+    for i in reader.walkFiles:
+      yield i.splitPath().tail
+    reader.close()
+
+elif thebundletype == Exe:
+
+  import std/compilesettings
+  import std/tables
+  import std/sequtils
+
+  proc loadResources(): Table[string, Table[string, string]] {.compileTime.} =
 
-proc bundleResourcesDirectory() {.compiletime.} =
-  # copy resource files to output directory
-  discard
-proc bundleResourcesZip() {.compiletime.} =
-  # copy resource files to zip in output directory
-  discard
-proc bundleResourcesExecutable() {.compiletime.} =
-  # put resource contents into variable here
-  discard
-proc bundleResources() {.compiletime.} =
-  when thebundletype == Dir: bundleResourcesDirectory()
-  elif thebundletype == Zip: bundleResourcesZip()
-  elif thebundletype == Exe: bundleResourcesExecutable()
+    let srcdir = joinPath(parentDir(querySetting(projectFull)), RESOURCEROOT)
+    for kind, moddir in walkDir(srcdir):
+      if kind == pcDir:
+        let modname = moddir.splitPath.tail
+        result[modname] = Table[string, string]()
+        for resourcefile in walkDirRec(moddir, relative=true):
+        # TODO: add Lempel–Ziv–Welch compression or something similar simple
+          result[modname][resourcefile] = staticRead(joinPath(moddir, resourcefile))
+  const bundledResources = loadResources()
+
+  proc loadResource_intern(path: string): Binary =
+    # TODO: add Lempel–Ziv–Welch compression or something similar simple
+    cast[seq[uint8]](bundledResources[selectedMod][path])
 
-static: bundleResources()
+  proc modList_intern(): seq[string] =
+    result = bundledResources.keys().toSeq()
+
+  iterator walkResources_intern(): string =
+    for i in bundledResources[selectedMod].keys:
+      yield i
+
+proc loadResource*(path: string): ref Binary =
+    result = new Binary
+    result[] = loadResource_intern(path)
+
+proc modList*(): seq[string] =
+  modList_intern()
+
+iterator walkResources*(): string =
+  for i in walkResources_intern():
+    yield i
--- a/tests/test_resources.nim	Tue May 09 20:39:49 2023 +0700
+++ b/tests/test_resources.nim	Wed May 10 22:36:59 2023 +0700
@@ -3,30 +3,12 @@
 import semicongine
 
 proc main() =
-  var scene = newScene("main", root=newEntity("rect", rect()))
-  var engine = initEngine("Test materials")
-  const
-    vertexInput = @[
-      attr[Vec3f]("position", memoryPerformanceHint=PreferFastRead),
-    ]
-    fragOutput = @[attr[Vec4f]("color")]
-    vertexCode = compileGlslShader(
-      stage=VK_SHADER_STAGE_VERTEX_BIT,
-      inputs=vertexInput,
-      main="""gl_Position = vec4(position, 1.0);"""
-    )
-    fragmentCode = compileGlslShader(
-      stage=VK_SHADER_STAGE_FRAGMENT_BIT,
-      outputs=fragOutput,
-      main="""color = vec4(1, 0, 0, 1);"""
-    )
-  engine.setRenderer(engine.gpuDevice.simpleForwardRenderPass(vertexCode, fragmentCode))
-  engine.addScene(scene, vertexInput)
-  var t = cpuTime()
-  while engine.updateInputs() == Running and not engine.keyIsDown(Escape):
-    engine.renderScene(scene)
-  engine.destroy()
-
+  echo "Mods available: ", modList()
+  for modName in modList():
+    echo modName, ":"
+    selectedMod = modName
+    for i in walkResources():
+      echo "  ", i, ": ", loadResource(i)[]
 
 when isMainModule:
   main()