Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 library analyzer.src.summary.flat_buffers; | |
| 2 | |
| 3 import 'dart:collection'; | |
| 4 import 'dart:convert'; | |
| 5 import 'dart:typed_data'; | |
| 6 | |
| 7 /** | |
| 8 * A pointer to some data. | |
| 9 */ | |
| 10 class BufferPointer { | |
| 11 final ByteData _buffer; | |
| 12 final int _offset; | |
| 13 | |
| 14 factory BufferPointer.fromBytes(List<int> byteList, [int offset = 0]) { | |
| 15 ByteBuffer byteBuffer = new Int8List.fromList(byteList).buffer; | |
|
Paul Berry
2016/01/07 15:55:03
It seems unfortunate that this constructor always
scheglov
2016/01/07 22:07:40
Done.
If Uint8List is given, we create a view on i
| |
| 16 ByteData buf = new ByteData.view(byteBuffer); | |
| 17 return new BufferPointer._(buf, offset); | |
| 18 } | |
| 19 | |
| 20 BufferPointer._(this._buffer, this._offset); | |
| 21 | |
| 22 BufferPointer derefObject() => _derefSOffset(); | |
|
Paul Berry
2016/01/07 15:55:03
This is incorrect. Offsets to objects should be u
scheglov
2016/01/07 22:07:40
OK, rewritten to use backward building.
| |
| 23 | |
| 24 @override | |
| 25 String toString() => _offset.toString(); | |
| 26 | |
| 27 BufferPointer _advance(int delta) { | |
| 28 return new BufferPointer._(_buffer, _offset + delta); | |
| 29 } | |
| 30 | |
| 31 BufferPointer _derefSOffset() { | |
| 32 int sOffset = _getInt32(); | |
| 33 return _advance(sOffset); | |
|
Paul Berry
2016/01/07 15:55:03
This should be _advance(-sOffset). Signed offsets
| |
| 34 } | |
| 35 | |
| 36 int _getInt32([int delta = 0]) => | |
| 37 _buffer.getInt32(_offset + delta, Endianness.LITTLE_ENDIAN); | |
| 38 | |
| 39 int _getInt8([int delta = 0]) => _buffer.getInt8(_offset + delta); | |
| 40 | |
| 41 int _getUint16([int delta = 0]) => | |
| 42 _buffer.getUint16(_offset + delta, Endianness.LITTLE_ENDIAN); | |
| 43 | |
| 44 int _getUint32([int delta = 0]) => | |
| 45 _buffer.getUint32(_offset + delta, Endianness.LITTLE_ENDIAN); | |
| 46 } | |
| 47 | |
| 48 /** | |
| 49 * Class that helps building flat buffers. | |
| 50 */ | |
| 51 class Builder { | |
| 52 final int initialSize; | |
| 53 | |
| 54 ByteData _buf; | |
| 55 int _offset; | |
| 56 | |
| 57 int _currentTableOffset; | |
| 58 _VTableBuilder _currentVTableBuilder; | |
| 59 | |
| 60 Builder({this.initialSize: 1024}) { | |
| 61 reset(); | |
| 62 } | |
| 63 | |
| 64 /** | |
| 65 * Add the [field] with the given 32-bit signed integer [value]. The field is | |
| 66 * not added if the [value] is equal to [def]. | |
| 67 */ | |
| 68 void addInt32(int field, int value, [int def]) { | |
| 69 if (_currentVTableBuilder == null) { | |
| 70 throw new StateError('Start a table before adding values.'); | |
| 71 } | |
| 72 if (value != def) { | |
| 73 int size = 4; | |
| 74 _align(size); | |
| 75 _trackField(field); | |
| 76 _ensure(size); | |
| 77 _buf.setInt32(_offset, value, Endianness.LITTLE_ENDIAN); | |
| 78 _offset += size; | |
| 79 } | |
| 80 } | |
| 81 | |
| 82 /** | |
| 83 * Add the [field] with the given 8-bit signed integer [value]. The field is | |
| 84 * not added if the [value] is equal to [def]. | |
| 85 */ | |
| 86 void addInt8(int field, int value, [int def]) { | |
| 87 if (_currentVTableBuilder == null) { | |
| 88 throw new StateError('Start a table before adding values.'); | |
| 89 } | |
| 90 if (value != def) { | |
| 91 int size = 1; | |
| 92 _align(size); | |
| 93 _trackField(field); | |
| 94 _ensure(size); | |
| 95 _buf.setInt8(_offset, value); | |
| 96 _offset += size; | |
| 97 } | |
| 98 } | |
| 99 | |
| 100 /** | |
| 101 * Add the [field] referencing an object with the given [offset]. | |
| 102 */ | |
| 103 void addOffset(int field, Offset offset) { | |
| 104 if (_currentVTableBuilder == null) { | |
| 105 throw new StateError('Start a table before adding values.'); | |
| 106 } | |
| 107 if (offset != null) { | |
| 108 int size = 4; | |
| 109 _align(size); | |
| 110 _trackField(field); | |
| 111 _ensure(size); | |
| 112 _buf.setInt32( | |
|
Paul Berry
2016/01/07 15:55:03
This isn't right. Offsets are unsigned and always
| |
| 113 _offset, offset._offset - _offset, Endianness.LITTLE_ENDIAN); | |
| 114 _offset += size; | |
| 115 } | |
| 116 } | |
| 117 | |
| 118 /** | |
| 119 * End the current table and return its offset. | |
| 120 */ | |
| 121 Offset endTable() { | |
| 122 if (_currentVTableBuilder == null) { | |
| 123 throw new StateError('Start a table before ending it.'); | |
| 124 } | |
| 125 // Prepare the size of the current table. | |
| 126 int tableSize = _offset - _currentTableOffset; | |
| 127 // Prepare for writing the VTable. | |
|
Paul Berry
2016/01/07 15:55:03
Add a TODO comment to note that we shouldn't outpu
scheglov
2016/01/07 22:07:40
Done.
| |
| 128 _align(2); | |
| 129 int vTableOffset = _offset; | |
| 130 // Write the VTable. | |
| 131 int vTableSize = _currentVTableBuilder.size; | |
| 132 _ensure(vTableSize); | |
| 133 _offset = _currentVTableBuilder.output(_buf, _offset, tableSize); | |
| 134 // Set the VTable offset. | |
| 135 _buf.setInt32(_currentTableOffset, vTableOffset - _currentTableOffset, | |
|
Paul Berry
2016/01/07 15:55:03
This should be _currentTableOffset - vTableOffset.
| |
| 136 Endianness.LITTLE_ENDIAN); | |
| 137 // Done with this table. | |
| 138 _currentVTableBuilder = null; | |
| 139 return new Offset(_currentTableOffset); | |
| 140 } | |
| 141 | |
| 142 /** | |
| 143 * Finish off the creation of the buffer. The given [offset] is used as the | |
| 144 * root object offset, and usually references directly of indirectly every | |
|
Paul Berry
2016/01/07 15:55:03
s/of/or/
scheglov
2016/01/07 22:07:39
Done.
| |
| 145 * written object. | |
| 146 */ | |
| 147 Uint8List finish(Offset offset) { | |
| 148 _buf.setUint32(0, offset._offset, Endianness.LITTLE_ENDIAN); | |
| 149 return _buf.buffer.asUint8List(0, _offset); | |
| 150 } | |
| 151 | |
| 152 /** | |
| 153 * Reset the builder and make it ready for filling a new buffer. | |
| 154 */ | |
| 155 void reset() { | |
| 156 _offset = 0; | |
| 157 _buf = new ByteData(initialSize); | |
| 158 _currentVTableBuilder = null; | |
| 159 // Skip the root offset. | |
| 160 _align(4); | |
| 161 _ensure(4); | |
| 162 _buf.setUint32(_offset, -1, Endianness.LITTLE_ENDIAN); | |
|
Paul Berry
2016/01/07 15:55:03
It seems weird to pass a literal -1 to setUint32,
| |
| 163 _offset += 4; | |
| 164 } | |
| 165 | |
| 166 /** | |
| 167 * Start a new table. Must be finished with [endTable] invocation. | |
| 168 */ | |
| 169 void startTable() { | |
| 170 if (_currentVTableBuilder != null) { | |
| 171 throw new StateError('Inline tables are not supported.'); | |
| 172 } | |
| 173 _currentVTableBuilder = new _VTableBuilder(); | |
| 174 // Skip the VTable offset. | |
| 175 _align(4); | |
| 176 _currentTableOffset = _offset; | |
| 177 _buf.setUint32(_offset, -1, Endianness.LITTLE_ENDIAN); | |
|
Paul Berry
2016/01/07 15:55:03
Similar comment here.
| |
| 178 _offset += 4; | |
| 179 } | |
| 180 | |
| 181 /** | |
| 182 * Write the given list of [values]. | |
| 183 */ | |
| 184 Offset writeList(List<Offset> values) { | |
| 185 if (_currentVTableBuilder != null) { | |
| 186 throw new StateError( | |
| 187 'Cannot write a non-scalar value while writing a table.'); | |
| 188 } | |
| 189 _align(4); | |
| 190 Offset<String> result = new Offset(_offset); | |
| 191 _ensure(4 + values.length); | |
| 192 _buf.setUint32(_offset, values.length, Endianness.LITTLE_ENDIAN); | |
| 193 _offset += 4; | |
| 194 for (Offset value in values) { | |
| 195 _buf.setInt32(_offset, value._offset - _offset, Endianness.LITTLE_ENDIAN); | |
|
Paul Berry
2016/01/07 15:55:03
As with line 112 this disagrees with the flatbuffe
| |
| 196 _offset += 4; | |
| 197 } | |
| 198 return result; | |
| 199 } | |
| 200 | |
| 201 /** | |
| 202 * Write the given string [value] and return its [Offset], or `null` if | |
| 203 * the [value] is equal to [def]. | |
| 204 */ | |
| 205 Offset<String> writeString(String value, [String def]) { | |
| 206 if (_currentVTableBuilder != null) { | |
| 207 throw new StateError( | |
| 208 'Cannot write a non-scalar value while writing a table.'); | |
| 209 } | |
| 210 if (value != def) { | |
| 211 List<int> bytes = UTF8.encode(value); | |
| 212 _align(4); | |
| 213 Offset<String> result = new Offset(_offset); | |
| 214 _ensure(4 + bytes.length); | |
| 215 _buf.setUint32(_offset, bytes.length, Endianness.LITTLE_ENDIAN); | |
| 216 _offset += 4; | |
| 217 for (int b in bytes) { | |
|
Paul Berry
2016/01/07 15:55:03
In practice UTF8.encode() will always return a Uin
scheglov
2016/01/07 22:07:40
I measured times to write using this cycle vs. usi
Paul Berry
2016/01/07 22:44:53
Fair enough. Thanks for checking.
| |
| 218 _buf.setInt8(_offset++, b); | |
| 219 } | |
| 220 return result; | |
| 221 } | |
| 222 return null; | |
| 223 } | |
| 224 | |
| 225 /** | |
| 226 * Prepare to write an element with the given [size]. The current offset is | |
| 227 * aligned to the given [size]. | |
| 228 */ | |
| 229 void _align(int size) { | |
| 230 int alignDelta = (-_offset) % size; | |
| 231 _ensure(alignDelta + size); | |
| 232 _offset += alignDelta; | |
| 233 } | |
| 234 | |
| 235 /** | |
| 236 * Ensure that there is at least the [size] bytes before the buffer end. | |
| 237 */ | |
| 238 void _ensure(int size) { | |
| 239 int newSize = _offset + size; | |
| 240 int oldCapacity = _buf.lengthInBytes; | |
| 241 if (newSize > oldCapacity) { | |
| 242 int newCapacity = newSize * 2; | |
| 243 ByteData newBuf = new ByteData(newCapacity); | |
| 244 newBuf.buffer.asUint8List().setAll(0, _buf.buffer.asUint8List()); | |
| 245 _buf = newBuf; | |
| 246 } | |
| 247 } | |
| 248 | |
| 249 /** | |
| 250 * Record the offset of the given [field]. | |
| 251 */ | |
| 252 void _trackField(int field) { | |
| 253 _currentVTableBuilder.addField(field, _offset - _currentTableOffset); | |
| 254 } | |
| 255 } | |
| 256 | |
| 257 /** | |
| 258 * The reader of 32-bit signed integers. | |
| 259 */ | |
| 260 class Int32Reader extends Reader<int> { | |
| 261 const Int32Reader() : super(); | |
| 262 | |
| 263 @override | |
| 264 int get size => 2; | |
| 265 | |
| 266 @override | |
| 267 int read(BufferPointer bp) => bp._getInt32(); | |
| 268 } | |
| 269 | |
| 270 /** | |
| 271 * The reader of 8-bit signed integers. | |
| 272 */ | |
| 273 class Int8Reader extends Reader<int> { | |
| 274 const Int8Reader() : super(); | |
| 275 | |
| 276 @override | |
| 277 int get size => 1; | |
| 278 | |
| 279 @override | |
| 280 int read(BufferPointer bp) => bp._getInt8(); | |
| 281 } | |
| 282 | |
| 283 /** | |
| 284 * The reader of object. | |
| 285 * | |
| 286 * The returned unmodifiable lists lazily read objects on access. | |
| 287 */ | |
| 288 class ListReader<E> extends Reader<List<E>> { | |
| 289 final Reader<E> _elementReader; | |
| 290 | |
| 291 const ListReader(this._elementReader); | |
| 292 | |
| 293 @override | |
| 294 int get size => 4; | |
| 295 | |
| 296 @override | |
| 297 List<E> read(BufferPointer bp) => | |
| 298 new _FbList<E>(_elementReader, bp.derefObject()); | |
| 299 } | |
| 300 | |
| 301 /** | |
| 302 * The absolute offset to a serialized object of the type [T]. | |
| 303 */ | |
| 304 class Offset<T> { | |
| 305 final int _offset; | |
| 306 | |
| 307 Offset(this._offset); | |
| 308 } | |
| 309 | |
| 310 /** | |
| 311 * Object that can read a value at a [BufferPointer]. | |
| 312 */ | |
| 313 abstract class Reader<T> { | |
| 314 const Reader(); | |
| 315 | |
| 316 /** | |
| 317 * The size of the value in bytes. | |
| 318 */ | |
| 319 int get size; | |
| 320 | |
| 321 /** | |
| 322 * Read the value at the given pointer. | |
| 323 */ | |
| 324 T read(BufferPointer bp); | |
| 325 | |
| 326 /** | |
| 327 * Read the value of the given [field] in the given [object]. | |
| 328 */ | |
| 329 T vTableGet(BufferPointer object, int field, [T defaultValue]) { | |
| 330 BufferPointer vTable = object._derefSOffset(); | |
| 331 int vTableSize = vTable._getUint16(); | |
| 332 int vTableFieldOffset = (1 + 1 + field) * 2; | |
| 333 if (vTableFieldOffset < vTableSize) { | |
| 334 int fieldOffsetInObject = vTable._getUint16(vTableFieldOffset); | |
| 335 if (fieldOffsetInObject != 0) { | |
| 336 BufferPointer fieldPointer = object._advance(fieldOffsetInObject); | |
| 337 return read(fieldPointer); | |
| 338 } | |
| 339 } | |
| 340 return defaultValue; | |
| 341 } | |
| 342 } | |
| 343 | |
| 344 /** | |
| 345 * The reader of string values. | |
| 346 */ | |
| 347 class StringReader extends Reader<String> { | |
| 348 const StringReader() : super(); | |
| 349 | |
| 350 @override | |
| 351 int get size => 4; | |
| 352 | |
| 353 @override | |
| 354 String read(BufferPointer ref) { | |
| 355 BufferPointer object = ref.derefObject(); | |
| 356 int length = object._getUint32(); | |
| 357 return UTF8 | |
| 358 .decode(ref._buffer.buffer.asUint8List(object._offset + 4, length)); | |
| 359 } | |
| 360 } | |
| 361 | |
| 362 /** | |
| 363 * An abstract reader for tables. | |
| 364 */ | |
| 365 abstract class TableReader<T extends TableReader<T>> extends Reader<T> { | |
| 366 const TableReader(); | |
| 367 | |
| 368 @override | |
| 369 int get size => 4; | |
| 370 | |
| 371 /** | |
| 372 * Return the [Reader] for reading fields of the object at [bp]. | |
| 373 */ | |
| 374 T createReader(BufferPointer bp); | |
| 375 | |
| 376 @override | |
| 377 T read(BufferPointer bp) { | |
| 378 bp = bp.derefObject(); | |
| 379 return createReader(bp); | |
| 380 } | |
| 381 } | |
| 382 | |
| 383 class _FbList<E> extends Object with ListMixin<E> implements List<E> { | |
| 384 final Reader<E> elementReader; | |
| 385 final BufferPointer bp; | |
| 386 | |
| 387 _FbList(this.elementReader, this.bp); | |
| 388 | |
| 389 @override | |
| 390 int get length => bp._getUint32(); | |
| 391 | |
| 392 @override | |
| 393 void set length(int i) => | |
| 394 throw new StateError('Attempt to modify immutable list'); | |
| 395 | |
| 396 @override | |
| 397 E operator [](int i) { | |
| 398 BufferPointer ref = bp._advance(4 + elementReader.size * i); | |
| 399 return elementReader.read(ref); | |
| 400 } | |
| 401 | |
| 402 @override | |
| 403 void operator []=(int i, E e) => | |
| 404 throw new StateError('Attempt to modify immutable list'); | |
| 405 } | |
| 406 | |
| 407 /** | |
| 408 * Class for building VTable(s). | |
| 409 */ | |
| 410 class _VTableBuilder { | |
| 411 final List<int> fieldOffsets = <int>[]; | |
| 412 | |
| 413 int get size => (1 + 1 + fieldOffsets.length) * 2; | |
| 414 | |
| 415 void addField(int field, int offset) { | |
| 416 while (fieldOffsets.length <= field) { | |
| 417 fieldOffsets.add(null); | |
| 418 } | |
| 419 fieldOffsets[field] = offset; | |
| 420 } | |
| 421 | |
| 422 /** | |
| 423 * Outputs this VTable to [buf], which is is expected to be aligned and | |
| 424 * have at least [size] bytes available. Return the offset directly after | |
| 425 * the VTable in [buf]. | |
| 426 */ | |
| 427 int output(ByteData buf, int bufOffset, int tableSize) { | |
| 428 buf.setUint16(bufOffset, size, Endianness.LITTLE_ENDIAN); | |
| 429 bufOffset += 2; | |
| 430 buf.setUint16(bufOffset, tableSize, Endianness.LITTLE_ENDIAN); | |
| 431 bufOffset += 2; | |
| 432 for (int fieldOffset in fieldOffsets) { | |
| 433 buf.setUint16(bufOffset, fieldOffset ?? 0, Endianness.LITTLE_ENDIAN); | |
| 434 bufOffset += 2; | |
| 435 } | |
| 436 return bufOffset; | |
| 437 } | |
| 438 } | |
| OLD | NEW |