3 serialization/[common, binary, text],
10 SomeSerializer* = BinSerializer | TextSerializer
12 # this is only used for triggerdata, everything else is done at compile time
13 method serializeDynamic*(self: Serializable, stream: BinSerializer) {.base.} = raiseBaseMethodCall("serializeDynamic")
14 method serializeDynamic*(self: Serializable, stream: TextSerializer) {.base.} = raiseBaseMethodCall("serializeDynamic")
16 proc serialize*[T: Serializable](self: SomeSerializer, blockName: string, blockId: int, value: T) =
17 if self.beginBlockType(blockName, blockId):
18 if self.beginBlockItem(blockName, blockId, value):
19 when value.hasCustomPragma(triggerData):
20 value.serializeDynamic(self) # each triggerdata subtype can provide its own serialization method
26 proc serialize*[T: Serializable](self: SomeSerializer, blockName: string, blockId: int, values: ref seq[T]) =
27 # in text format each object has its own block, while in binary they're grouped in big blocks
28 if self.isWriting and values[].len() == 0:
29 return # don't write empty blocks
30 if self.beginBlockType(blockName, blockId):
32 for v in values[].mitems():
33 if self.beginBlockItem(blockName, blockId, v):
37 # scan until we stop getting blocks of requested type
38 # TODO: this entire autistic scheme doesn't support putting different block types and fields out of order
40 while self.inBlockType(blockName, blockId):
41 let oldLen = values[].len()
42 values[].setLen(oldLen + 1)
43 values[oldLen] = new(T)
45 values[oldLen].id = cast[type(values[oldLen].id)](oldLen) # default to index-based names in case we're reading from binary
46 values[oldLen].name = blockName & $oldLen
47 if self.beginBlockItem(blockName, blockId, values[oldLen]):
48 self.serialize(values[oldLen])
52 # generic proc for serializing records
53 proc serialize*[T: Serializable](stream: SomeSerializer, self: T) =
54 for origFieldName, fieldSym in fieldPairs(self[]):
55 when not (fieldSym.hasCustomPragma(serializeNever) or (stream is BinSerializer and fieldSym.hasCustomPragma(serializeTextOnly))):
56 # rename field if needed
57 when fieldSym.hasCustomPragma(serializedFieldName):
58 const fieldName = fieldSym.getCustomPragmaVal(serializedFieldName)
59 elif origFieldName == "kind":
60 const fieldName = "type" # HACK: `type` is reserved, so instead of spamming pragmas everywhere we do this
62 const fieldName = origFieldName.snakeCase()
63 # check if we should alaways expect this field to be there
64 when fieldSym.hasCustomPragma(writeDefault):
65 let mandatory = (stream is BinSerializer) or stream.isWriting # all binary fields are mandatory
67 when fieldSym.hasCustomPragma(defaultValue):
68 let defaultVal = cast[type(fieldSym)](fieldSym.getCustomPragmaVal(defaultValue))
69 if not stream.isWriting: fieldSym = defaultVal # preinit with custom default value if there is one
71 let defaultVal = default(type(fieldSym))
72 let mandatory = (stream is BinSerializer) or (stream.isWriting and fieldSym != defaultVal)
73 # actual serialization
74 when fieldSym is string:
75 when fieldSym.hasCustomPragma(maxSize):
76 const strSize = fieldSym.getCustomPragmaVal(maxSize)
78 const strSize = 256 # FIXME
79 if stream.isWriting or mandatory or stream.checkNextField(fieldName):
80 stream.serialize(fieldName, fieldSym, strSize)
81 when fieldSym.hasCustomPragma(resPath):
82 # normalize resource paths when reading them
83 if not stream.isWriting:
84 fieldSym = fieldSym.normalizeDFPath()
85 elif fieldSym is ref seq:
86 when fieldSym.hasCustomPragma(serializedBlockId):
87 const blkId = fieldSym.getCustomPragmaVal(serializedBlockId)
90 when fieldSym.hasCustomPragma(serializedBlockName):
91 const blkName = fieldSym.getCustomPragmaVal(serializedBlockName)
94 stream.serialize(blkName, blkId, fieldSym)
95 elif fieldSym is Serializable:
96 when fieldSym.hasCustomPragma(serializedBlockId):
97 const blkId = fieldSym.getCustomPragmaVal(serializedBlockId)
100 when fieldSym.hasCustomPragma(serializedBlockName):
101 const blkName = fieldSym.getCustomPragmaVal(serializedBlockName)
104 when blkId != -1 or blkName != "":
105 # this is a header or something to that effect; they're always mandatory
106 if not stream.isWriting and fieldSym == nil:
107 # create and default-init object if it doesn't exist yet
108 when fieldSym.hasCustomPragma(triggerData):
109 fieldSym = self.newTriggerData() # call parent to provide appropriate triggerdata for us
111 new(fieldSym) # let the type system figure that shit out
113 stream.serialize(blkName, blkId, fieldSym)
115 # this is a direct reference to another serializable
116 if mandatory or (not stream.isWriting and stream.checkNextField(fieldName)):
117 # HACK: if reading make a placeholder because this stupid fucking macro doesn't provide `var T` fields
118 if not stream.isWriting and fieldSym == nil:
120 fieldSym.placeholder = true
121 stream.serializeId(fieldName, fieldSym)
122 elif not (fieldSym is ref):
124 if mandatory or (not stream.isWriting and stream.checkNextField(fieldName)):
125 stream.serialize(fieldName, fieldSym)
126 # sometimes shit is misaligned, so padding is needed
127 when stream is BinSerializer and fieldSym.hasCustomPragma(overrideSize):
128 stream.zeroPad(fieldSym.getCustomPragmaVal(overrideSize) - sizeof(fieldSym))