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) |