sizer.nim
  1  {.push raises: [], gcsafe.}
  2  
  3  import
  4    std/[typetraits, tables],
  5    stew/shims/macros,
  6    serialization,
  7    "."/[codec, internal, types]
  8  
  9  func computeObjectSize*[T: object](value: T): int
 10  
 11  func computeFieldSize(
 12      fieldNum: int, fieldVal: auto, ProtoType: type UnsupportedType,
 13      _: static bool) =
 14    # TODO turn this into an extension point
 15    unsupportedProtoType ProtoType.FieldType, ProtoType.RootType, ProtoType.fieldName
 16  
 17  func computeFieldSize[T: object and not PBOption](
 18      fieldNum: int, fieldVal: T, ProtoType: type pbytes,
 19      skipDefault: static bool): int =
 20    let
 21      size = computeObjectSize(fieldVal)
 22  
 23    when skipDefault:
 24      if size == 0:
 25        return 0
 26  
 27    computeSize(FieldHeader.init(fieldNum, ProtoType.wireKind())) +
 28      computeSize(puint64(size)) +
 29      size
 30  
 31  proc computeFieldSize*[T: not object](
 32      fieldNum: int, fieldVal: T,
 33      ProtoType: type SomeScalar, skipDefault: static bool): int =
 34    when skipDefault:
 35      const def = default(typeof(fieldVal))
 36      if fieldVal == def:
 37        return
 38  
 39    computeSize(fieldNum, ProtoType(fieldVal))
 40  
 41  proc computeFieldSize*(
 42      fieldNum: int, fieldVal: PBOption, ProtoType: type,
 43      skipDefault: static bool): int =
 44    if fieldVal.isSome(): # TODO required field checking
 45      computeFieldSize(fieldNum, fieldVal.get(), ProtoType, skipDefault)
 46    else:
 47      0
 48  
 49  when defined(ConformanceTest):
 50    proc computeFieldSize*[T](
 51      fieldNum: int, fieldVal: ref T,
 52      ProtoType: type pbytes, skipDefault: static bool): int =
 53      if not fieldVal.isNil():
 54        computeFieldSize(fieldNum, fieldVal[], ProtoType, skipDefault)
 55      else:
 56        0
 57  
 58    proc writeField[T: enum](
 59        stream: OutputStream, fieldNum: int, fieldVal: T, ProtoType: type) =
 60      when 0 notin T:
 61        {.fatal: $T & " definition must contain a constant that maps to zero".}
 62      stream.writeField(fieldNum, pint32(fieldVal.ord()))
 63  
 64    proc computeFieldSize*[K, V](
 65        fieldNum: int, fieldVal: Table[K, V], ProtoType: type pbytes,
 66        skipDefault: static bool): int =
 67      tableObject(TableObject, K, V)
 68      for k, v in fieldVal.pairs():
 69        let tmp = TableObject(key: k, value: v)
 70        result += computeFieldSize(fieldNum, tmp, ProtoType, false)
 71  
 72  proc computeSizePacked*[T: not byte, ProtoType: SomePrimitive](
 73      values: openArray[T], _: type ProtoType): int =
 74    const canCopyMem =
 75      ProtoType is SomeFixed32 or ProtoType is SomeFixed64 or ProtoType is pbool
 76    when canCopyMem:
 77      values.len() * sizeof(T)
 78    else:
 79      var total = 0
 80      for item in values:
 81        total += computeSize(ProtoType(item))
 82      total
 83  
 84  proc computeFieldSizePacked*[ProtoType: SomePrimitive](
 85      field: int, values: openArray, _: type ProtoType): int =
 86    # Packed encoding uses a length-delimited field byte length of the sum of the
 87    # byte lengths of each field followed by the header-free contents
 88    let
 89      dataSize = computeSizePacked(values, ProtoType)
 90  
 91    computeSize(FieldHeader.init(field, WireKind.LengthDelim)) +
 92      computeSize(puint64(dataSize)) +
 93      dataSize
 94  
 95  func computeObjectSize*[T: object](value: T): int =
 96    const
 97      isProto2: bool = T.isProto2()
 98      isProto3: bool = T.isProto3()
 99    static:
100      doAssert isProto2 xor isProto3
101  
102    var total = 0
103    enumInstanceSerializedFields(value, fieldName, fieldVal):
104      const
105        fieldNum = T.fieldNumberOf(fieldName)
106  
107      type
108        FlatType = flatType(fieldVal)
109  
110      protoType(ProtoType, T, FlatType, fieldName)
111  
112      let fieldSize = when FlatType is seq and FlatType isnot seq[byte]:
113        const
114          isPacked = T.isPacked(fieldName).get(isProto3)
115        when isPacked and ProtoType is SomePrimitive:
116          computeFieldSizePacked(fieldNum, fieldVal, ProtoType)
117        else:
118          var dataSize = 0
119          for i in 0..<fieldVal.len:
120            # don't skip defaults so as to preserve length
121            dataSize += computeFieldSize(fieldNum, fieldVal[i], ProtoType, false)
122          dataSize
123  
124      else:
125        computeFieldSize(fieldNum, fieldVal, ProtoType, isProto3)
126  
127      total += fieldSize
128  
129    total