Skip to content

Commit f5b8eb6

Browse files
committed
dagon.core.persistent
1 parent 01bc7e3 commit f5b8eb6

File tree

3 files changed

+165
-1
lines changed

3 files changed

+165
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
Dagon 0.19.0 - TBD
22
------------------
33
- **Core**
4+
- `dagon.core.persistent` - simple file-based key-value database that can be used to manage application-specific user data, such as preferences and game saves
45
- `InputManager` now supports whitespace-separated keycode names in bindings. Instead of the whitespace, `+` sign is used, e.g. `kb_left+ctrl` for `left ctrl`
56
- `Application` now sets console encoding to UTF-8 under Windows
67
- **Rendering**

src/dagon/core/package.d

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright (c) 2019-2022 Timur Gafarov
2+
Copyright (c) 2019-2025 Timur Gafarov
33
44
Boost Software License - Version 1.0 - August 17th, 2003
55
Permission is hereby granted, free of charge, to any person or organization
@@ -36,6 +36,7 @@ public
3636
import dagon.core.input;
3737
import dagon.core.keycodes;
3838
import dagon.core.locale;
39+
import dagon.core.persistent;
3940
import dagon.core.props;
4041
import dagon.core.time;
4142
import dagon.core.vfs;

src/dagon/core/persistent.d

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
Copyright (c) 2025 Timur Gafarov
3+
4+
Boost Software License - Version 1.0 - August 17th, 2003
5+
Permission is hereby granted, free of charge, to any person or organization
6+
obtaining a copy of the software and accompanying documentation covered by
7+
this license (the "Software") to use, reproduce, display, distribute,
8+
execute, and transmit the Software, and to prepare derivative works of the
9+
Software, and to permit third-parties to whom the Software is furnished to
10+
do so, all subject to the following:
11+
12+
The copyright notices in the Software and this entire statement, including
13+
the above license grant, this restriction and the following disclaimer,
14+
must be included in all copies of the Software, in whole or in part, and
15+
all derivative works of the Software, unless such copies or derivative
16+
works are solely in the form of machine-executable object code generated by
17+
a source language processor.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22+
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23+
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25+
DEALINGS IN THE SOFTWARE.
26+
*/
27+
28+
module dagon.core.persistent;
29+
30+
import std.stdio: writeln;
31+
import std.traits;
32+
import std.file;
33+
import std.process;
34+
35+
import dlib.core.memory;
36+
import dlib.core.ownership;
37+
import dlib.core.stream;
38+
import dlib.math.vector;
39+
import dlib.image.color;
40+
import dlib.text.str;
41+
import dlib.filesystem.filesystem;
42+
import dlib.filesystem.stdfs;
43+
44+
import dagon.core.props;
45+
46+
/*
47+
* Persistent key-value data storage, uses the same format as dagon.core.config.
48+
* Files are stored in home directory under Posix and in %APPDATA% directory
49+
* under Windows (C:\Users\<user>\AppData\Roaming).
50+
* It can be used to manage application-specific user data, such as preferences
51+
* and game saves.
52+
*/
53+
class PersistentStorage: Owner
54+
{
55+
StdFileSystem fs;
56+
String appDataDir;
57+
String filename;
58+
String localFilename;
59+
Properties props;
60+
61+
this(string appFolder, string filename, Owner o)
62+
{
63+
super(o);
64+
fs = New!StdFileSystem();
65+
66+
string homeDirVar = "";
67+
version(Windows) homeDirVar = "APPDATA";
68+
version(Posix) homeDirVar = "~";
69+
70+
string dirSeparator;
71+
version(Windows) dirSeparator = "\\";
72+
version(Posix) dirSeparator = "/";
73+
74+
appDataDir = String(environment.get(homeDirVar, ""));
75+
if (appDataDir.length)
76+
{
77+
// Use <appDataDir>/<appFolder>/<filename>
78+
String output;
79+
this.filename = String(appDataDir);
80+
this.filename ~= dirSeparator;
81+
this.filename ~= appFolder;
82+
83+
fs.createDir(this.filename, true);
84+
85+
this.filename ~= dirSeparator;
86+
this.filename ~= filename;
87+
}
88+
else
89+
{
90+
// Use ./<filename>
91+
this.filename = String(filename);
92+
}
93+
94+
this.localFilename = String(filename);
95+
96+
writeln("Using persistent storage file: ", this.filename);
97+
98+
props = New!Properties(this);
99+
load();
100+
}
101+
102+
~this()
103+
{
104+
appDataDir.free();
105+
filename.free();
106+
localFilename.free();
107+
Delete(fs);
108+
}
109+
110+
void load()
111+
{
112+
FileStat stat;
113+
if (fs.stat(filename, stat))
114+
{
115+
auto istrm = fs.openForInput(filename);
116+
auto input = readText(istrm);
117+
Delete(istrm);
118+
props.parse(input);
119+
Delete(input);
120+
}
121+
}
122+
123+
void save()
124+
{
125+
String data = props.serialize();
126+
127+
OutputStream strm = fs.openForOutput(filename, FileSystem.create | FileSystem.truncate);
128+
strm.writeArray(data);
129+
strm.close();
130+
Delete(strm);
131+
132+
data.free();
133+
}
134+
135+
DProperty opDispatch(string key)()
136+
{
137+
return props[key];
138+
}
139+
140+
void opDispatch(string key, T)(T value)
141+
{
142+
bool changed = false;
143+
static if (is(T == string))
144+
{
145+
props.set(DPropType.Number, key, value);
146+
changed = true;
147+
}
148+
else static if (is(T == Vector3f) || is(T == Vector4f) || is(T == Color4f))
149+
{
150+
props.set(DPropType.Vector, key, value.toString());
151+
changed = true;
152+
}
153+
else static if (isNumeric!T)
154+
{
155+
props.set(DPropType.Number, key, value.to!string);
156+
changed = true;
157+
}
158+
159+
if (changed)
160+
save();
161+
}
162+
}

0 commit comments

Comments
 (0)