Mercurial > games > semicongine
comparison semiconginev2/old/settings.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/settings.nim@a3eb305bcac2 |
| children |
comparison
equal
deleted
inserted
replaced
| 1217:f819a874058f | 1218:56781cc0fc7c |
|---|---|
| 1 import std/logging | |
| 2 import std/streams | |
| 3 import std/parsecfg | |
| 4 import std/strutils | |
| 5 import std/parseutils | |
| 6 import std/strformat | |
| 7 import std/tables | |
| 8 import std/os | |
| 9 | |
| 10 import ./core | |
| 11 | |
| 12 when CONFIGHOTRELOAD: | |
| 13 var | |
| 14 configUpdates: Channel[(string, string)] | |
| 15 configUpdates.open() | |
| 16 | |
| 17 # runtime configuration | |
| 18 # ===================== | |
| 19 # namespace is the path from the CONFIGROOT to the according settings file without the file extension | |
| 20 # a settings file must always have the extension CONFIGEXTENSION | |
| 21 # a fully qualified settings identifier can be in the form {namespace}.{section}.{key} | |
| 22 # {key} and {section} may not contain dots | |
| 23 | |
| 24 # a "namespace" is the path from the settings root to an *.CONFIGEXTENSION file, without the file extension | |
| 25 # settings is a namespace <-> settings mapping | |
| 26 var allsettings: Table[string, Config] | |
| 27 | |
| 28 proc configRoot(): string = | |
| 29 joinPath(absolutePath(getAppDir()), CONFIGROOT) | |
| 30 | |
| 31 proc getFile(namespace: string): string = | |
| 32 joinPath(configRoot(), namespace & "." & CONFIGEXTENSION) | |
| 33 | |
| 34 iterator walkConfigNamespaces(): string = | |
| 35 for file in walkDirRec(dir = configRoot(), relative = true, checkDir = true): | |
| 36 if file.endsWith("." & CONFIGEXTENSION): | |
| 37 yield file[0 ..< ^(CONFIGEXTENSION.len + 1)] | |
| 38 | |
| 39 proc loadAllConfig(): Table[string, Config] = | |
| 40 for ns in walkConfigNamespaces(): | |
| 41 result[ns] = ns.getFile().loadConfig() | |
| 42 | |
| 43 proc ReloadSettings*() = | |
| 44 allsettings = loadAllConfig() | |
| 45 | |
| 46 proc configStr(key, section, namespace: string): string = | |
| 47 when CONFIGHOTRELOAD: | |
| 48 while configUpdates.peek() > 0: | |
| 49 let (updatedNamespace, updatedConfig) = configUpdates.recv() | |
| 50 allsettings[updatedNamespace] = loadConfig(newStringStream(updatedConfig)) | |
| 51 if not allsettings.hasKey(namespace): | |
| 52 raise newException(Exception, &"Settings {namespace}.{section}.{key} was not found") | |
| 53 allsettings[namespace].getSectionValue(section, key) | |
| 54 | |
| 55 proc Setting*[T: int|float|string](key, section, namespace: string): T = | |
| 56 when T is int: | |
| 57 let value = configStr(key, section, namespace) | |
| 58 if parseInt(value, result) == 0: | |
| 59 raise newException(Exception, &"Unable to parse int from settings {namespace}.{section}.{key}: {value}") | |
| 60 elif T is float: | |
| 61 let value = configStr(key, section, namespace) | |
| 62 if parseFloat(value, result) == 0: | |
| 63 raise newException(Exception, &"Unable to parse float from settings {namespace}.{section}.{key}: {value}") | |
| 64 else: | |
| 65 result = configStr(key, section, namespace) | |
| 66 | |
| 67 proc Setting*[T: int|float|string](identifier: string): T = | |
| 68 # identifier can be in the form: | |
| 69 # {namespace}.{key} | |
| 70 # {namespace}.{section}.{key} | |
| 71 let parts = identifier.rsplit(".") | |
| 72 if parts.len == 1: | |
| 73 raise newException(Exception, &"Setting with name {identifier} has no namespace") | |
| 74 if parts.len == 2: result = Setting[T](parts[1], "", parts[0]) | |
| 75 else: result = Setting[T](parts[^1], parts[^2], joinPath(parts[0 .. ^3])) | |
| 76 | |
| 77 proc HadConfigUpdate*(): bool = | |
| 78 when CONFIGHOTRELOAD == true: | |
| 79 result = configUpdates.peek() > 0 | |
| 80 | |
| 81 allsettings = loadAllConfig() | |
| 82 | |
| 83 when CONFIGHOTRELOAD == true: | |
| 84 import std/times | |
| 85 | |
| 86 proc configFileWatchdog() {.thread.} = | |
| 87 var configModTimes: Table[string, Time] | |
| 88 while true: | |
| 89 for namespace in walkConfigNamespaces(): | |
| 90 if not (namespace in configModTimes): | |
| 91 configModTimes[namespace] = Time() | |
| 92 let lastMod = namespace.getFile().getLastModificationTime() | |
| 93 if lastMod > configModTimes[namespace]: | |
| 94 configModTimes[namespace] = lastMod | |
| 95 let configStr = newFileStream(namespace.getFile()).readAll() | |
| 96 configUpdates.send((namespace, configStr)) | |
| 97 sleep CONFIGHOTRELOADINTERVAL | |
| 98 var thethread: Thread[void] | |
| 99 createThread(thethread, configFileWatchdog) | |
| 100 | |
| 101 if DEBUG: | |
| 102 setLogFilter(lvlAll) | |
| 103 else: | |
| 104 setLogFilter(lvlWarn) |
