1 /* 2 * Copyright 2016 Google Inc. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 module flatbuffers.flatbufferbuilder; 18 19 import flatbuffers.exception; 20 import flatbuffers.bytebuffer; 21 22 import std.exception; 23 import std.traits : isNumeric; 24 25 /** 26 Responsible for building up and accessing a FlatBuffer formatted byte 27 */ 28 final class FlatBufferBuilder 29 { 30 /** 31 Create a FlatBufferBuilder with a given initial size. 32 Params: 33 initsize = The initial size to use for the internal buffer. 34 */ 35 this(size_t initsize = 32) 36 in{ 37 assert(initsize > 0); 38 }body{ 39 this(new ByteBuffer(new ubyte[initsize])); 40 } 41 42 this(ByteBuffer buffer){ 43 _space = buffer.length; 44 _buffer = buffer; 45 } 46 47 uint offset() 48 { 49 return cast(uint)(_buffer.length - _space); 50 } 51 52 void pad(size_t size) 53 { 54 for (int i = 0; i < size; i++) 55 { 56 --_space; 57 _buffer.put!ubyte(_space, 0x00); 58 } 59 } 60 61 /** Doubles the size of the ByteBuffer, and copies the old data towards 62 the end of the new buffer (since we build the buffer backwards). 63 */ 64 void growBuffer() 65 { 66 auto oldBuf = _buffer.data; 67 auto oldBufSize = oldBuf.length; 68 if ((oldBufSize & 0xC0000000) != 0) 69 throw new Exception("FlatBuffers: cannot grow buffer beyond 2 gigabytes."); 70 71 auto newBufSize = oldBufSize >= 32 ? oldBufSize * 2 : 64; 72 auto newBuf = new ubyte[](newBufSize); 73 newBuf[(newBufSize - oldBufSize) .. $] = oldBuf[]; 74 _buffer.restData(newBuf,0); 75 } 76 77 /** 78 Prepare to write an element of `size` after `additional_bytes` 79 have been written, e.g. if you write a string, you need to align 80 such the int length field is aligned to SIZEOF_INT, and the string 81 data follows it directly. 82 If all you need to do is align, `additional_bytes` will be 0. 83 */ 84 void prep(size_t size, size_t additionalBytes) 85 { 86 // Track the biggest thing we've ever aligned to. 87 if (size > _minAlign) 88 _minAlign = size; 89 90 // Find the amount of alignment needed such that `size` is properly 91 // aligned after `additional_bytes`. 92 auto alignSize = ((~( _buffer.length - _space + additionalBytes)) + 1) & (size - 1); 93 94 // Reallocate the buffer if needed. 95 while (_space < alignSize + size + additionalBytes) 96 { 97 auto oldBufSize = cast(int) _buffer.length; 98 growBuffer(); 99 _space += cast(int) _buffer.length - oldBufSize; 100 } 101 if (alignSize > 0) 102 pad(alignSize); 103 } 104 105 /** 106 put a value into the buffer. 107 */ 108 void put(T)(T x) if (is(T == bool) || isNumeric!T) 109 { 110 static if (is(T == bool)) 111 { 112 _space -= 1; 113 } 114 else 115 { 116 _space -= T.sizeof; 117 } 118 _buffer.put!T(_space, x); 119 } 120 121 /// Adds a scalar to the buffer, properly aligned, and the buffer grown if needed. 122 void add(T)(T x)if (is(T == bool) || isNumeric!T) 123 { 124 static if (is(T == bool)) 125 prep(1, 0); 126 else 127 prep(T.sizeof, 0); 128 put!T(x); 129 } 130 /// Adds on offset, relative to where it will be written. 131 void addOffset(uint off) 132 { 133 prep(uint.sizeof, 0); // Ensure alignment is already done. 134 if (off > offset()) 135 throw new ArgumentException("FlatBuffers: must be less than offset.", "off"); 136 137 off = offset() - off + cast(uint)uint.sizeof; 138 put!uint(off); 139 } 140 141 void startVector(int elemSize, int count, int alignment) 142 { 143 notNested(); 144 _vectorNumElems = count; 145 prep(int.sizeof, elemSize * count); 146 prep(alignment, elemSize * count); // Just in case alignment > int. 147 } 148 149 uint endVector() 150 { 151 put!int(cast(int)_vectorNumElems); 152 return offset(); 153 } 154 155 void nested(int obj) 156 { 157 // Structs are always stored inline, so need to be created right 158 // where they are used. You'll get this assert if you created it 159 // elsewhere. 160 if (obj != offset()) 161 throw new Exception("FlatBuffers: struct must be serialized inline."); 162 } 163 164 void notNested() 165 { 166 // You should not be creating any other objects or strings/vectors 167 // while an object is being constructed. 168 if (_vtable) 169 throw new Exception("FlatBuffers: object serialization must not be nested."); 170 } 171 172 void startObject(int numfields) 173 { 174 if (numfields < 0) 175 throw new ArgumentOutOfRangeException("numfields", numfields, 176 "must be greater than zero"); 177 178 notNested(); 179 _vtable = new size_t[](numfields); 180 _objectStart = offset(); 181 } 182 183 /// Set the current vtable at `voffset` to the current location in the buffer. 184 void slot(size_t voffset) 185 { 186 _vtable[voffset] = offset(); 187 } 188 189 /// Add a scalar to a table at `o` into its vtable, with value `x` and default `d`. 190 void add(T : bool)(size_t o, T x, T d) 191 { 192 if (x != d) 193 { 194 add!T(x); 195 slot(o); 196 } 197 } 198 /// ditto 199 void add(T)(size_t o, T x, T d) if(isNumeric!T) 200 { 201 if (x != d) 202 { 203 add!T(x); 204 slot(o); 205 } 206 } 207 /// ditto 208 void addOffset(int o, int x, int d) 209 { 210 if (x != d) 211 { 212 addOffset(x); 213 slot(o); 214 } 215 } 216 217 /** Structs are stored inline, so nothing additional is being added. 218 `d` is always 0. 219 */ 220 void addStruct(int voffset, int x, int d) 221 { 222 if (x != d) 223 { 224 nested(x); 225 slot(voffset); 226 } 227 } 228 229 /** 230 Encode the string `s` in the buffer using UTF-8. 231 Params: 232 s = The string to encode. 233 Returns: 234 The offset in the buffer where the encoded string starts. 235 */ 236 uint createString(string s) 237 { 238 notNested(); 239 auto utf8 = cast(ubyte[]) s; 240 add!ubyte(cast(ubyte) 0); 241 startVector(1, cast(int) utf8.length, 1); 242 _space -= utf8.length; 243 _buffer.data[_space .. _space + utf8.length] = utf8[]; 244 return endVector(); 245 } 246 247 uint endObject() 248 { 249 if (!_vtable) 250 throw new InvalidOperationException( 251 "Flatbuffers: calling endObject without a startObject"); 252 253 add!int(cast(int) 0); 254 auto vtableloc = offset(); 255 256 // Write out the current vtable. 257 for (int i = cast(int) _vtable.length - 1; i >= 0; i--) 258 { 259 // Offset relative to the start of the table. 260 short off = cast(short)(_vtable[i] != 0 ? vtableloc - _vtable[i] : 0); 261 add!short(off); 262 } 263 264 const int standardFields = 2; // The fields below: 265 add!short(cast(short)(vtableloc - _objectStart)); 266 add!short(cast(short)((_vtable.length + standardFields) * short.sizeof)); 267 268 /// Search for an existing vtable that matches the current one. 269 size_t existingVtable = 0; 270 271 ubyte[] data = _buffer.data(); 272 273 for (int i = 0; i < _numVtables; i++) 274 { 275 auto vt1 = _buffer.length - _vtables[i]; 276 auto vt2 = _space; 277 short vt1len = _buffer.get!short(vt1); 278 short vt2len = _buffer.get!short(vt2); 279 280 if (vt1len != vt2len || data[vt1 .. (vt1 + vt1len)] != data[vt2 .. (vt2 + vt2len)]) 281 continue; 282 existingVtable = _vtables[i]; 283 } 284 285 if (existingVtable != 0) 286 { 287 // Found a match: 288 // Remove the current vtable. 289 _space = _buffer.length - vtableloc; 290 // Point table to existing vtable. 291 _buffer.put!int(_space, cast(int)(existingVtable - vtableloc)); 292 } 293 else 294 { 295 // No match: 296 // Add the location of the current vtable to the list of vtables. 297 if (_numVtables == _vtables.length) 298 _vtables.length *= 2; 299 _vtables[_numVtables++] = offset(); 300 // Point table to current vtable. 301 _buffer.put!int(_buffer.length - vtableloc, offset() - vtableloc); 302 } 303 304 destroy(_vtable); 305 _vtable = null; 306 return vtableloc; 307 } 308 309 /** This checks a required field has been set in a given table that has 310 just been constructed. 311 */ 312 void required(int table, int field) 313 { 314 import std.string; 315 316 auto table_start = _buffer.length - table; 317 auto vtable_start = table_start - _buffer.get!int(table_start); 318 bool ok = _buffer.get!short(vtable_start + field) != 0; 319 // If this fails, the caller will show what field needs to be set. 320 if (!ok) 321 throw new InvalidOperationException(format("FlatBuffers: field %s must be set.", 322 field)); 323 } 324 325 /** 326 Finalize a buffer, pointing to the given `root_table`. 327 Params: 328 rootTable = An offset to be added to the buffer. 329 */ 330 void finish(int rootTable) 331 { 332 prep(_minAlign, int.sizeof); 333 addOffset(rootTable); 334 } 335 336 /** 337 Get the ByteBuffer representing the FlatBuffer. 338 Notes: his is typically only called after you call `Finish()`. 339 Returns: 340 Returns the ByteBuffer for this FlatBuffer. 341 */ 342 ByteBuffer dataBuffer() 343 { 344 return _buffer; 345 } 346 347 /** 348 A utility function to copy and return the ByteBuffer data as a `ubyte[]` 349 Retuens: 350 the byte used in FlatBuffer data, it is not copy. 351 */ 352 ubyte[] sizedByteArray() 353 { 354 return _buffer.data[_buffer.position .. $]; 355 } 356 357 /** 358 Finalize a buffer, pointing to the given `rootTable`. 359 Params: 360 rootTable = An offset to be added to the buffer. 361 fileIdentifier = A FlatBuffer file identifier to be added to the buffer before `root_table`. 362 */ 363 void finish(int rootTable, string fileIdentifier) 364 { 365 import std.string; 366 367 prep(_minAlign, int.sizeof + fileIdentifierLength); 368 if (fileIdentifier.length != fileIdentifierLength) 369 throw new ArgumentException( 370 format("FlatBuffers: file identifier must be length %s.", fileIdentifierLength), 371 "fileIdentifier"); 372 for (int i = fileIdentifierLength - 1; i >= 0; i--) 373 add!ubyte(cast(ubyte) fileIdentifier[i]); 374 addOffset(rootTable); 375 } 376 377 private: 378 size_t _space; 379 ByteBuffer _buffer; 380 size_t _minAlign = 1; 381 382 /// The vtable for the current table, null otherwise. 383 size_t[] _vtable; 384 /// Starting offset of the current struct/table. 385 size_t _objectStart; 386 /// List of offsets of all vtables. 387 size_t[] _vtables = new int[](16); 388 /// Number of entries in `vtables` in use. 389 size_t _numVtables = 0; 390 /// For the current vector being built. 391 size_t _vectorNumElems = 0; 392 }