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