Mercurial > games > semicongine
view semicongine/resources.nim @ 1104:ed79529e70a3
fix: packaging fails if there are no resources, fix: zip-generation
author | sam <sam@basx.dev> |
---|---|
date | Fri, 12 Apr 2024 19:36:38 +0700 |
parents | 10da4ef8d9e1 |
children | 967b8fa81b6b |
line wrap: on
line source
import std/parsecfg import std/streams import std/algorithm import std/json import std/strutils import std/sequtils import std/strformat import std/os import std/sets import std/unicode import ./core import ./resources/image import ./resources/audio import ./resources/mesh import ./resources/font import ./mesh import ./material export image export audio export mesh type ResourceBundlingType = enum Dir # Directories Zip # Zip files Exe # Embeded in executable const thebundletype = parseEnum[ResourceBundlingType](PACKAGETYPE.toLowerAscii().capitalizeAscii()) ASCII_CHARSET = PrintableChars.toSeq.toRunes DEFAULT_PACKAGE = "default" # resource loading func normalizeDir(dir: string): string = result = dir if result.startsWith("./"): result = result[2 .. ^1] if result.startsWith("/"): result = result[1 .. ^1] result = dir.replace('\\', '/') if not result.endsWith("/") and result != "": result = result & "/" when thebundletype == Dir: proc resourceRoot(): string = getAppDir().absolutePath().joinPath(RESOURCEROOT) proc packageRoot(package: string): string = resourceRoot().joinPath(package) proc loadResource_intern(path: string, package: string): Stream = let realpath = package.packageRoot().joinPath(path) if not realpath.fileExists(): raise newException(Exception, &"Resource {path} not found (checked {realpath})") newFileStream(realpath, fmRead) proc modList_intern(): seq[string] = for kind, file in walkDir(resourceRoot(), relative = true): if kind == pcDir: result.add file iterator walkResources_intern(dir: string, package = DEFAULT_PACKAGE): string = for file in walkDirRec(package.packageRoot().joinPath(dir), relative = true): yield file iterator ls_intern(dir: string, package: string): tuple[kind: PathComponent, path: string] = for i in walkDir(package.packageRoot().joinPath(dir), relative = true): yield i elif thebundletype == Zip: import zippy/ziparchives proc resourceRoot(): string = absolutePath(getAppDir()).joinPath(RESOURCEROOT) proc packageRoot(package: string): string = resourceRoot().joinPath(package) proc loadResource_intern(path: string, package: string): Stream = let archive = openZipArchive(package.packageRoot() & ".zip") try: result = newStringStream(archive.extractFile(path)) except ZippyError: raise newException(Exception, &"Resource {path} not found") archive.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] iterator walkResources_intern(dir: string, package = DEFAULT_PACKAGE): string = let archive = openZipArchive(package.packageRoot() & ".zip") let normDir = dir.normalizeDir() for i in archive.walkFiles: if i.startsWith(normDir): yield i archive.close() iterator ls_intern(dir: string, package: string): tuple[kind: PathComponent, path: string] = let archive = openZipArchive(package.packageRoot() & ".zip") let normDir = dir.normalizeDir() var yielded: HashSet[string] for i in archive.walkFiles: if i.startsWith(normDir): let components = i[normDir.len .. ^1].split('/', maxsplit = 1) if components.len == 1: if not (components[0] in yielded): yield (kind: pcFile, path: components[0]) else: if not (components[0] in yielded): yield (kind: pcDir, path: components[0]) yielded.incl components[0] archive.close() elif thebundletype == Exe: import std/tables const BUILD_RESOURCEROOT* {.strdefine.}: string = "" proc loadResources(): Table[string, Table[string, string]] {.compileTime.} = when BUILD_RESOURCEROOT == "": {.warning: "BUILD_RESOURCEROOT is empty, no resources will be packaged".} return else: for kind, packageDir in walkDir(BUILD_RESOURCEROOT): if kind == pcDir: let package = packageDir.splitPath.tail result[package] = Table[string, string]() for resourcefile in walkDirRec(packageDir, relative = true): result[package][resourcefile.replace('\\', '/')] = staticRead(packageDir.joinPath(resourcefile)) const bundledResources = loadResources() proc loadResource_intern(path: string, package: string): Stream = if not (path in bundledResources[package]): raise newException(Exception, &"Resource {path} not found") newStringStream(bundledResources[package][path]) proc modList_intern(): seq[string] = result = bundledResources.keys().toSeq() iterator walkResources_intern(dir: string, package = DEFAULT_PACKAGE): string = for i in bundledResources[package].keys: yield i iterator ls_intern(dir: string, package: string): tuple[kind: PathComponent, path: string] = let normDir = dir.normalizeDir() var yielded: HashSet[string] for i in bundledResources[package].keys: if i.startsWith(normDir): let components = i[normDir.len .. ^1].split('/', maxsplit = 1) if components.len == 1: if not (components[0] in yielded): yield (kind: pcFile, path: components[0]) else: if not (components[0] in yielded): yield (kind: pcDir, path: components[0]) yielded.incl components[0] proc loadResource*(path: string, package = DEFAULT_PACKAGE): Stream = loadResource_intern(path, package = package) proc loadImage*[T](path: string, package = DEFAULT_PACKAGE): Image[RGBAPixel] = if path.splitFile().ext.toLowerAscii == ".bmp": loadResource_intern(path, package = package).readBMP() elif path.splitFile().ext.toLowerAscii == ".png": loadResource_intern(path, package = package).readPNG() else: raise newException(Exception, "Unsupported image file type: " & path) proc loadAudio*(path: string, package = DEFAULT_PACKAGE): Sound = if path.splitFile().ext.toLowerAscii == ".au": loadResource_intern(path, package = package).readAU() elif path.splitFile().ext.toLowerAscii == ".ogg": loadResource_intern(path, package = package).readVorbis() else: raise newException(Exception, "Unsupported audio file type: " & path) proc loadJson*(path: string, package = DEFAULT_PACKAGE): JsonNode = path.loadResource_intern(package = package).readAll().parseJson() proc loadConfig*(path: string, package = DEFAULT_PACKAGE): Config = path.loadResource_intern(package = package).loadConfig(filename = path) proc loadFont*( path: string, name = "", lineHeightPixels = 80'f32, additional_codepoints: openArray[Rune] = [], charset = ASCII_CHARSET, package = DEFAULT_PACKAGE ): Font = var thename = name if thename == "": thename = path.splitFile().name loadResource_intern(path, package = package).readTrueType(name, charset & additional_codepoints.toSeq, lineHeightPixels) proc loadMeshes*(path: string, defaultMaterial: MaterialType, package = DEFAULT_PACKAGE): seq[MeshTree] = loadResource_intern(path, package = package).readglTF(defaultMaterial) proc loadFirstMesh*(path: string, defaultMaterial: MaterialType, package = DEFAULT_PACKAGE): Mesh = loadResource_intern(path, package = package).readglTF(defaultMaterial)[0].toSeq[0] proc packages*(): seq[string] = modList_intern() proc walkResources*(dir = "", package = DEFAULT_PACKAGE): seq[string] = for i in walkResources_intern(dir, package = package): if i.startsWith(dir): result.add i result.sort() proc ls*(dir: string, package = DEFAULT_PACKAGE): seq[tuple[kind: PathComponent, path: string]] = for i in ls_intern(dir = dir, package = package): result.add i result.sort()