Mercurial > games > semicongine
changeset 1464:3e3192241ea7 default tip
add: API for world/level storage
author | sam <sam@basx.dev> |
---|---|
date | Mon, 24 Mar 2025 22:57:47 +0700 |
parents | f9d86889e018 |
children | |
files | semicongine/core/types.nim semicongine/storage.nim tests/test_storage.nim |
diffstat | 3 files changed, 95 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- a/semicongine/core/types.nim Mon Mar 24 00:15:08 2025 +0700 +++ b/semicongine/core/types.nim Mon Mar 24 22:57:47 2025 +0700 @@ -475,8 +475,8 @@ # === storage === StorageType* = enum - SystemStorage - UserStorage # ? level storage type ? + SystemStorage # usually device-specific settings + UserStorage # usually user specific settings (should be synced) # === steam === SteamUserStatsRef* = ptr object
--- a/semicongine/storage.nim Mon Mar 24 00:15:08 2025 +0700 +++ b/semicongine/storage.nim Mon Mar 24 22:57:47 2025 +0700 @@ -1,8 +1,11 @@ import std/marshal import std/os +import std/dirs import std/paths import std/strformat +import std/strutils import std/tables +import std/times import ./core @@ -11,6 +14,12 @@ const STORAGE_NAME = Path("storage.db") const DEFAULT_KEY_VALUE_TABLE_NAME = "shelf" +# ============================================================== +# +# API to store key/value pairs +# +# ============================================================== + proc path(storageType: StorageType): Path = case storageType of SystemStorage: @@ -75,3 +84,62 @@ proc purge*(storageType: StorageType) = storageType.path().string.removeFile() + +# ============================================================== +# +# API to store "worlds", which is one database per "world" +# +# ============================================================== + +const DEFAULT_WORLD_TABLE_NAME = "world" +const WORLD_DIR = "worlds" + +proc path(worldName: string): Path = + let dir = Path(getDataDir()) / Path(AppName()) / Path(WORLD_DIR) + string(dir).createDir() + dir / Path(worldName & ".db") + +proc ensureExists(worldName: string): DbConn = + open(string(worldName.path), "", "", "") + +proc ensureExists(worldName: string, table: string): DbConn = + result = worldName.ensureExists() + result.exec( + sql( + &"""CREATE TABLE IF NOT EXISTS {table} ( + key INT NOT NULL UNIQUE, + value TEXT NOT NULL + )""" + ) + ) + +proc storeWorld*[T]( + worldName: string, world: T, table = DEFAULT_WORLD_TABLE_NAME, deleteOld = false +) = + let db = worldName.ensureExists(table) + defer: + db.close() + let key = $(int(now().toTime().toUnixFloat() * 1000)) + db.exec(sql(&"""INSERT INTO {table} VALUES(?, ?)"""), key, $$world) + db.exec(sql(&"""DELETE FROM {table} WHERE key <> ?"""), key) + +proc loadWorld*[T](worldName: string, table = DEFAULT_WORLD_TABLE_NAME): T = + let db = worldName.ensureExists(table) + defer: + db.close() + let dbResult = + db.getValue(sql(&"""SELECT value FROM {table} ORDER BY key DESC LIMIT 1""")) + return to[T](dbResult) + +proc listWorlds*(): seq[string] = + let dir = Path(getDataDir()) / Path(AppName()) / Path(WORLD_DIR) + + if dir.dirExists(): + for (kind, path) in walkDir( + dir = string(dir), relative = true, checkDir = true, skipSpecial = true + ): + if kind in [pcFile, pcLinkToFile] and path.endsWith(".db"): + result.add path[0 .. ^4] + +proc purgeWorld*(worldName: string) = + worldName.path().string.removeFile()
--- a/tests/test_storage.nim Mon Mar 24 00:15:08 2025 +0700 +++ b/tests/test_storage.nim Mon Mar 24 22:57:47 2025 +0700 @@ -14,6 +14,28 @@ store(storage, KEY, TEST_VALUE) assert storage.load(KEY, 0) == TEST_VALUE +proc testWorldAPI() = + assert listWorlds().len == 0 + + "testWorld".storeWorld(42) + assert listWorlds() == @["testWorld"] + assert loadWorld[int]("testWorld") == 42 + + "testWorld".storeWorld("hello") + assert listWorlds() == @["testWorld"] + assert loadWorld[string]("testWorld") == "hello" + + "earth".storeWorld("hello") + assert "earth" in listWorlds() + assert "testWorld" in listWorlds() + assert loadWorld[string]("earth") == "hello" + + "earth".purgeWorld() + assert listWorlds() == @["testWorld"] + + "testWorld".purgeWorld() + assert listWorlds().len == 0 + proc stressTest(storage: StorageType) = for i in 1 .. 10000: let key = &"key-{i}" @@ -30,6 +52,9 @@ echo "UserStorage: Testing simple store/load" UserStorage.testSimple() + echo "Testing world-storage API" + testWorldAPI() + echo "Stress test with 10'000 saves/loads" SystemStorage.stressTest()