Mercurial > games > semicongine
comparison semiconginev2/old/resources.nim @ 1218:56781cc0fc7c compiletime-tests
did: renamge main package
| author | sam <sam@basx.dev> |
|---|---|
| date | Wed, 17 Jul 2024 21:01:37 +0700 |
| parents | semicongine/old/resources.nim@a3eb305bcac2 |
| children |
comparison
equal
deleted
inserted
replaced
| 1217:f819a874058f | 1218:56781cc0fc7c |
|---|---|
| 1 import std/parsecfg | |
| 2 import std/streams | |
| 3 import std/algorithm | |
| 4 import std/json | |
| 5 import std/strutils | |
| 6 import std/sequtils | |
| 7 import std/strformat | |
| 8 import std/os | |
| 9 import std/sets | |
| 10 import std/unicode | |
| 11 | |
| 12 import ./core | |
| 13 import ./resources/image | |
| 14 import ./resources/audio | |
| 15 import ./resources/mesh | |
| 16 import ./resources/font | |
| 17 import ./mesh | |
| 18 import ./material | |
| 19 | |
| 20 export image | |
| 21 export audio | |
| 22 export mesh | |
| 23 | |
| 24 type | |
| 25 ResourceBundlingType = enum | |
| 26 Dir # Directories | |
| 27 Zip # Zip files | |
| 28 Exe # Embeded in executable | |
| 29 | |
| 30 const | |
| 31 thebundletype = parseEnum[ResourceBundlingType](PACKAGETYPE.toLowerAscii().capitalizeAscii()) | |
| 32 ASCII_CHARSET = PrintableChars.toSeq.toRunes | |
| 33 DEFAULT_PACKAGE = "default" | |
| 34 | |
| 35 # resource loading | |
| 36 | |
| 37 func normalizeDir(dir: string): string = | |
| 38 result = dir | |
| 39 if result.startsWith("./"): | |
| 40 result = result[2 .. ^1] | |
| 41 if result.startsWith("/"): | |
| 42 result = result[1 .. ^1] | |
| 43 result = dir.replace('\\', '/') | |
| 44 if not result.endsWith("/") and result != "": | |
| 45 result = result & "/" | |
| 46 | |
| 47 when thebundletype == Dir: | |
| 48 | |
| 49 proc resourceRoot(): string = | |
| 50 getAppDir().absolutePath().joinPath(RESOURCEROOT) | |
| 51 proc packageRoot(package: string): string = | |
| 52 resourceRoot().joinPath(package) | |
| 53 | |
| 54 proc loadResource_intern(path: string, package: string): Stream = | |
| 55 let realpath = package.packageRoot().joinPath(path) | |
| 56 if not realpath.fileExists(): | |
| 57 raise newException(Exception, &"Resource {path} not found (checked {realpath})") | |
| 58 newFileStream(realpath, fmRead) | |
| 59 | |
| 60 proc modList_intern(): seq[string] = | |
| 61 for kind, file in walkDir(resourceRoot(), relative = true): | |
| 62 if kind == pcDir: | |
| 63 result.add file | |
| 64 | |
| 65 iterator walkResources_intern(dir: string, package = DEFAULT_PACKAGE): string = | |
| 66 for file in walkDirRec(package.packageRoot().joinPath(dir), relative = true): | |
| 67 yield file | |
| 68 | |
| 69 iterator ls_intern(dir: string, package: string): tuple[kind: PathComponent, path: string] = | |
| 70 for i in walkDir(package.packageRoot().joinPath(dir), relative = true): | |
| 71 yield i | |
| 72 | |
| 73 elif thebundletype == Zip: | |
| 74 | |
| 75 import ./thirdparty/zippy/zippy/ziparchives | |
| 76 | |
| 77 proc resourceRoot(): string = | |
| 78 absolutePath(getAppDir()).joinPath(RESOURCEROOT) | |
| 79 proc packageRoot(package: string): string = | |
| 80 resourceRoot().joinPath(package) | |
| 81 | |
| 82 proc loadResource_intern(path: string, package: string): Stream = | |
| 83 let archive = openZipArchive(package.packageRoot() & ".zip") | |
| 84 try: | |
| 85 result = newStringStream(archive.extractFile(path)) | |
| 86 except ZippyError: | |
| 87 raise newException(Exception, &"Resource {path} not found") | |
| 88 archive.close() | |
| 89 | |
| 90 proc modList_intern(): seq[string] = | |
| 91 for kind, file in walkDir(resourceRoot(), relative = true): | |
| 92 if kind == pcFile and file.endsWith(".zip"): | |
| 93 result.add file[0 ..< ^4] | |
| 94 | |
| 95 iterator walkResources_intern(dir: string, package = DEFAULT_PACKAGE): string = | |
| 96 let archive = openZipArchive(package.packageRoot() & ".zip") | |
| 97 let normDir = dir.normalizeDir() | |
| 98 for i in archive.walkFiles: | |
| 99 if i.startsWith(normDir): | |
| 100 yield i | |
| 101 archive.close() | |
| 102 | |
| 103 iterator ls_intern(dir: string, package: string): tuple[kind: PathComponent, path: string] = | |
| 104 let archive = openZipArchive(package.packageRoot() & ".zip") | |
| 105 let normDir = dir.normalizeDir() | |
| 106 var yielded: HashSet[string] | |
| 107 | |
| 108 for i in archive.walkFiles: | |
| 109 if i.startsWith(normDir): | |
| 110 let components = i[normDir.len .. ^1].split('/', maxsplit = 1) | |
| 111 if components.len == 1: | |
| 112 if not (components[0] in yielded): | |
| 113 yield (kind: pcFile, path: components[0]) | |
| 114 else: | |
| 115 if not (components[0] in yielded): | |
| 116 yield (kind: pcDir, path: components[0]) | |
| 117 yielded.incl components[0] | |
| 118 archive.close() | |
| 119 | |
| 120 elif thebundletype == Exe: | |
| 121 | |
| 122 import std/tables | |
| 123 | |
| 124 const BUILD_RESOURCEROOT* {.strdefine.}: string = "" | |
| 125 | |
| 126 proc loadResources(): Table[string, Table[string, string]] {.compileTime.} = | |
| 127 when BUILD_RESOURCEROOT == "": | |
| 128 {.warning: "BUILD_RESOURCEROOT is empty, no resources will be packaged".} | |
| 129 return | |
| 130 else: | |
| 131 for kind, packageDir in walkDir(BUILD_RESOURCEROOT): | |
| 132 if kind == pcDir: | |
| 133 let package = packageDir.splitPath.tail | |
| 134 result[package] = Table[string, string]() | |
| 135 for resourcefile in walkDirRec(packageDir, relative = true): | |
| 136 result[package][resourcefile.replace('\\', '/')] = staticRead(packageDir.joinPath(resourcefile)) | |
| 137 const bundledResources = loadResources() | |
| 138 | |
| 139 proc loadResource_intern(path: string, package: string): Stream = | |
| 140 if not (path in bundledResources[package]): | |
| 141 raise newException(Exception, &"Resource {path} not found") | |
| 142 newStringStream(bundledResources[package][path]) | |
| 143 | |
| 144 proc modList_intern(): seq[string] = | |
| 145 result = bundledResources.keys().toSeq() | |
| 146 | |
| 147 iterator walkResources_intern(dir: string, package = DEFAULT_PACKAGE): string = | |
| 148 for i in bundledResources[package].keys: | |
| 149 yield i | |
| 150 | |
| 151 iterator ls_intern(dir: string, package: string): tuple[kind: PathComponent, path: string] = | |
| 152 let normDir = dir.normalizeDir() | |
| 153 var yielded: HashSet[string] | |
| 154 | |
| 155 for i in bundledResources[package].keys: | |
| 156 if i.startsWith(normDir): | |
| 157 let components = i[normDir.len .. ^1].split('/', maxsplit = 1) | |
| 158 if components.len == 1: | |
| 159 if not (components[0] in yielded): | |
| 160 yield (kind: pcFile, path: components[0]) | |
| 161 else: | |
| 162 if not (components[0] in yielded): | |
| 163 yield (kind: pcDir, path: components[0]) | |
| 164 yielded.incl components[0] | |
| 165 | |
| 166 proc LoadResource*(path: string, package = DEFAULT_PACKAGE): Stream = | |
| 167 loadResource_intern(path, package = package) | |
| 168 | |
| 169 proc LoadImage*[T](path: string, package = DEFAULT_PACKAGE): Image[RGBAPixel] = | |
| 170 if path.splitFile().ext.toLowerAscii == ".bmp": | |
| 171 loadResource_intern(path, package = package).ReadBMP() | |
| 172 elif path.splitFile().ext.toLowerAscii == ".png": | |
| 173 loadResource_intern(path, package = package).ReadPNG() | |
| 174 else: | |
| 175 raise newException(Exception, "Unsupported image file type: " & path) | |
| 176 | |
| 177 proc LoadAudio*(path: string, package = DEFAULT_PACKAGE): Sound = | |
| 178 if path.splitFile().ext.toLowerAscii == ".au": | |
| 179 loadResource_intern(path, package = package).ReadAU() | |
| 180 elif path.splitFile().ext.toLowerAscii == ".ogg": | |
| 181 loadResource_intern(path, package = package).ReadVorbis() | |
| 182 else: | |
| 183 raise newException(Exception, "Unsupported audio file type: " & path) | |
| 184 | |
| 185 proc LoadJson*(path: string, package = DEFAULT_PACKAGE): JsonNode = | |
| 186 path.loadResource_intern(package = package).readAll().parseJson() | |
| 187 | |
| 188 proc LoadConfig*(path: string, package = DEFAULT_PACKAGE): Config = | |
| 189 path.loadResource_intern(package = package).loadConfig(filename = path) | |
| 190 | |
| 191 proc LoadFont*( | |
| 192 path: string, | |
| 193 name = "", | |
| 194 lineHeightPixels = 80'f32, | |
| 195 additional_codepoints: openArray[Rune] = [], | |
| 196 charset = ASCII_CHARSET, | |
| 197 package = DEFAULT_PACKAGE | |
| 198 ): Font = | |
| 199 var thename = name | |
| 200 if thename == "": | |
| 201 thename = path.splitFile().name | |
| 202 loadResource_intern(path, package = package).ReadTrueType(name, charset & additional_codepoints.toSeq, lineHeightPixels) | |
| 203 | |
| 204 proc LoadMeshes*(path: string, defaultMaterial: MaterialType, package = DEFAULT_PACKAGE): seq[MeshTree] = | |
| 205 loadResource_intern(path, package = package).ReadglTF(defaultMaterial) | |
| 206 | |
| 207 proc LoadFirstMesh*(path: string, defaultMaterial: MaterialType, package = DEFAULT_PACKAGE): Mesh = | |
| 208 loadResource_intern(path, package = package).ReadglTF(defaultMaterial)[0].toSeq[0] | |
| 209 | |
| 210 proc Packages*(): seq[string] = | |
| 211 modList_intern() | |
| 212 | |
| 213 proc WalkResources*(dir = "", package = DEFAULT_PACKAGE): seq[string] = | |
| 214 for i in walkResources_intern(dir, package = package): | |
| 215 if i.startsWith(dir): | |
| 216 result.add i | |
| 217 result.sort() | |
| 218 | |
| 219 proc List*(dir: string, package = DEFAULT_PACKAGE): seq[tuple[kind: PathComponent, path: string]] = | |
| 220 for i in ls_intern(dir = dir, package = package): | |
| 221 result.add i | |
| 222 result.sort() |
