view: render map entities
[d2df-maped.git] / src / serialization.nim
blob4e13471945841cbbf07c19c78114c11bf4bfc8c0
1 import
2   std/macros,
3   serialization/[common, binary, text],
4   utils
6 export
7   common, binary, text
9 type
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
21       else:
22         self.serialize(value)
23       self.endBlockItem()
24     self.endBlockType()
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):
31     if self.isWriting:
32       for v in values[].mitems():
33         if self.beginBlockItem(blockName, blockId, v):
34           self.serialize(v)
35           self.endBlockItem()
36     else:
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
39       values[].setLen(0)
40       while self.inBlockType(blockName, blockId):
41         let oldLen = values[].len()
42         values[].setLen(oldLen + 1)
43         values[oldLen] = new(T)
44         values[oldLen].init()
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])
49           self.endBlockItem()
50     self.endBlockType()
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
61       else:
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
66       else:
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
70         else:
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)
77         else:
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)
88         else:
89           const blkId = -1
90         when fieldSym.hasCustomPragma(serializedBlockName):
91           const blkName = fieldSym.getCustomPragmaVal(serializedBlockName)
92         else:
93           const blkName = ""
94         stream.serialize(blkName, blkId, fieldSym)
95       elif fieldSym is Serializable:
96         when fieldSym.hasCustomPragma(serializedBlockId):
97           const blkId = fieldSym.getCustomPragmaVal(serializedBlockId)
98         else:
99           const blkId = -1
100         when fieldSym.hasCustomPragma(serializedBlockName):
101           const blkName = fieldSym.getCustomPragmaVal(serializedBlockName)
102         else:
103           const blkName = ""
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
110             else:
111               new(fieldSym) # let the type system figure that shit out
112             fieldSym.init()
113           stream.serialize(blkName, blkId, fieldSym)
114         else:
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:
119               new(fieldSym)
120               fieldSym.placeholder = true
121             stream.serializeId(fieldName, fieldSym)
122       elif not (fieldSym is ref):
123         # primitive type
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))