Mercurial > games > semicongine
changeset 1468:cbca94a95736
add: custom serialization
author | sam <sam@basx.dev> |
---|---|
date | Tue, 01 Apr 2025 00:26:14 +0700 |
parents | 58b62be1902c |
children | c69bb7c58cf2 |
files | semicongine/storage.nim tests/test_storage.nim |
diffstat | 2 files changed, 86 insertions(+), 5 deletions(-) [+] |
line wrap: on
line diff
--- a/semicongine/storage.nim Wed Mar 26 23:40:05 2025 +0700 +++ b/semicongine/storage.nim Tue Apr 01 00:26:14 2025 +0700 @@ -2,10 +2,12 @@ import std/os import std/dirs import std/paths +import std/streams import std/strformat import std/strutils import std/tables import std/times +import std/typetraits import ./core @@ -113,21 +115,89 @@ ) ) +proc writeValue[T: object | tuple](s: Stream, data: T) +proc writeValue[T](s: Stream, value: openArray[T]) + +proc writeValue[T: SomeOrdinal | SomeFloat](s: Stream, value: T) = + s.write(value) + +proc writeValue(s: Stream, value: string) = + s.write(value.len.int32) + s.write(value) + +proc writeValue[T](s: Stream, value: openArray[T]) = + s.write(value.len.int32) + for v in value: + writeValue(s, v) + +proc writeValue[T: object | tuple](s: Stream, data: T) = + for field, value in data.fieldPairs(): + writeValue(s, value) + proc storeWorld*[T]( - worldName: string, world: T, table = DEFAULT_WORLD_TABLE_NAME, deleteOld = false + worldName: string, data: T, table = DEFAULT_WORLD_TABLE_NAME, deleteOld = false ) = + var s = newStringStream() + writeValue(s, data) + let data = newSeq[byte](s.getPosition()) + s.setPosition(0) + discard s.readData(addr(data[0]), data.len) + let db = worldName.ensureExists(table) defer: db.close() let key = $(int(now().toTime().toUnixFloat() * 1000)) let stm = db.prepare(&"""INSERT INTO {table} VALUES(?, ?)""") stm.bindParam(1, key) - stm.bindParam(2, $$world) + stm.bindParam(2, data) db.exec(stm) stm.finalize() if deleteOld: db.exec(sql(&"""DELETE FROM {table} WHERE key <> ?"""), key) +proc loadNumericValue[T: SomeOrdinal | SomeFloat](s: Stream): T = + read(s, result) + +proc loadSeqValue[T: seq](s: Stream): T = + var len: int32 + read(s, len) + for i in 0 ..< len: + var v: elementType(result) + read(s, v) + result.add v + +proc loadArrayValue[T: array](s: Stream): T = + var len: int32 + read(s, len) + doAssert len == len(result) + for i in 0 ..< len: + read(s, result[i]) + +proc loadStringValue(s: Stream): string = + var len: int32 + read(s, len) + readStr(s, len) + +proc loadValue[T](s: Stream): T + +proc loadObjectValue[T: object | tuple](s: Stream): T = + for field, value in result.fieldPairs(): + value = loadValue[typeof(value)](s) + +proc loadValue[T](s: Stream): T = + when T is SomeOrdinal or T is SomeFloat: + loadNumericValue[T](s) + elif T is seq: + loadSeqValue[T](s) + elif T is array: + loadArrayValue[T](s) + elif T is string: + loadStringValue(s) + elif T is object or T is tuple: + loadObjectValue[T](s) + else: + {.error: "Cannot load type " & $T.} + proc loadWorld*[T](worldName: string, table = DEFAULT_WORLD_TABLE_NAME): T = let db = worldName.ensureExists(table) defer: @@ -135,7 +205,8 @@ let dbResult = db.getValue(sql(&"""SELECT value FROM {table} ORDER BY key DESC LIMIT 1""")) - to[T](dbResult) + var s = newStringStream(dbResult) + loadValue[T](s) proc listWorlds*(): seq[string] = let dir = Path(getDataDir()) / Path(AppName()) / Path(WORLD_DIR)
--- a/tests/test_storage.nim Wed Mar 26 23:40:05 2025 +0700 +++ b/tests/test_storage.nim Tue Apr 01 00:26:14 2025 +0700 @@ -19,7 +19,11 @@ value: int type Obj2 = object - value: string + a: string + b: Obj1 + c: seq[int] + d: array[3, Obj1] + e: bool assert listWorlds().len == 0 @@ -28,7 +32,13 @@ assert listWorlds() == @["testWorld"] assert loadWorld[Obj1]("testWorld") == obj1 - const obj2 = Obj2(value: "Hello world") + const obj2 = Obj2( + a: "Hello world", + b: Obj1(value: 20), + c: @[1, 2, 3, 4], + d: [Obj1(value: 1), Obj1(value: 2), Obj1(value: 3)], + e: true, + ) "testWorld".storeWorld(obj2) assert listWorlds() == @["testWorld"] assert loadWorld[Obj2]("testWorld") == obj2