Address further review comments, further cleanup and add comments.
Made any classes and methods that can be private, private.
diff --git a/pkgs/dart_model/lib/src/json_buffer/closed_map.dart b/pkgs/dart_model/lib/src/json_buffer/closed_map.dart
index 0c38e02..4cfc983 100644
--- a/pkgs/dart_model/lib/src/json_buffer/closed_map.dart
+++ b/pkgs/dart_model/lib/src/json_buffer/closed_map.dart
@@ -18,12 +18,12 @@
static const _valueSize = _typeSize + _pointerSize;
static const _entrySize = _keySize + _valueSize;
- /// Adds a `Map` to the buffer, returns the [Pointer] to it.
+ /// Adds a `Map` to the buffer, returns the [_Pointer] to it.
///
/// The `Map` should be small and already evaluated, making it fast to
/// iterate. For large maps see "growable map" methods.
- Pointer _addClosedMap(Map<String, Object?> map) {
- explanations?.push('addClosedMap $map');
+ _Pointer _addClosedMap(Map<String, Object?> map) {
+ _explanations?.push('addClosedMap $map');
final length = map.length;
final pointer = _reserve(_lengthSize + length * _entrySize);
@@ -39,12 +39,12 @@
entryPointer += _entrySize;
}
- explanations?.pop();
+ _explanations?.pop();
return pointer;
}
/// Returns the [_ClosedMap] at [pointer].
- Map<String, Object?> readClosedMap(Pointer pointer) {
+ Map<String, Object?> _readClosedMap(_Pointer pointer) {
return _ClosedMap(this, pointer);
}
}
@@ -52,12 +52,12 @@
class _ClosedMap
with MapMixin<String, Object?>, _EntryMapMixin<String, Object?> {
final JsonBufferBuilder _buffer;
- final Pointer _pointer;
+ final _Pointer _pointer;
@override
final int length;
_ClosedMap(this._buffer, this._pointer)
- : length = _buffer.readUint32(_pointer);
+ : length = _buffer._readLength(_pointer);
@override
Object? operator [](Object? key) {
@@ -86,28 +86,31 @@
@override
void operator []=(String key, Object? value) {
- throw UnsupportedError('ClosedMap is readonly.');
+ throw UnsupportedError(
+ 'This JsonBufferBuilder map is read-only, see "createGrowableMap".');
}
@override
Object? remove(Object? key) {
- throw UnsupportedError('ClosedMap is readonly.');
+ throw UnsupportedError(
+ 'This JsonBufferBuilder map is read-only, see "createGrowableMap".');
}
@override
void clear() {
- throw UnsupportedError('ClosedMap is readonly.');
+ throw UnsupportedError(
+ 'This JsonBufferBuilder map is read-only, see "createGrowableMap".');
}
}
/// `Iterator` that reads a "closed map" in a [JsonBufferBuilder].
abstract class _ClosedMapIterator<T> implements Iterator<T> {
final JsonBufferBuilder _buffer;
- final Pointer _last;
+ final _Pointer _last;
- Pointer _pointer;
+ _Pointer _pointer;
- _ClosedMapIterator(this._buffer, Pointer pointer, int length)
+ _ClosedMapIterator(this._buffer, _Pointer pointer, int length)
: _last = pointer + _lengthSize + length * ClosedMaps._entrySize,
// Subtract because `moveNext` is called before reading.
_pointer = pointer + _lengthSize - ClosedMaps._entrySize;
@@ -115,7 +118,7 @@
@override
T get current;
- String get _currentKey => _buffer.readString(_buffer.readPointer(_pointer));
+ String get _currentKey => _buffer._readString(_buffer._readPointer(_pointer));
Object? get _currentValue => _buffer._readAny(_pointer + _pointerSize);
@override
diff --git a/pkgs/dart_model/lib/src/json_buffer/explanations.dart b/pkgs/dart_model/lib/src/json_buffer/explanations.dart
index 117bcfd..87839d9 100644
--- a/pkgs/dart_model/lib/src/json_buffer/explanations.dart
+++ b/pkgs/dart_model/lib/src/json_buffer/explanations.dart
@@ -4,20 +4,9 @@
part of 'json_buffer_builder.dart';
-/// Global hook for turning on "debug logs" for [JsonBufferBuilder].
-///
-/// Set to an instance of [Explanations] to turn on logging, reset to null to
-/// turn off logging. If set, [JsonBufferBuilder#toString] will print
-/// additional information for each byte.
-///
-/// TODO(davidmorgan): this is only intended for use in local unit tests.
-/// If debug logs are useful elsewhere, do something more robust, but take
-/// care not to degrade performance when logs are off.
-Explanations? explanations;
-
/// Tracks why each byte in a [JsonBufferBuilder] was written, and does
/// additional checks.
-class Explanations {
+class _Explanations {
final Map<int, String> _explanationsByPointer = {};
final List<String> _explanationsStack = [];
@@ -44,7 +33,7 @@
///
/// Set [allowOverwrite] if multiple writes to this location are allowed.
/// Otherwise, this method throws on multiple writes.
- void explain(Pointer pointer, {bool allowOverwrite = false}) {
+ void explain(_Pointer pointer, {bool allowOverwrite = false}) {
if (_explanationsStack.isNotEmpty) {
final explanation = _explanationsStack.join(', ') +
(allowOverwrite ? _allowOverwriteTag : '');
@@ -61,7 +50,7 @@
}
/// Associate the current explanation stack with all bytes [from] until [to].
- void explainRange(Pointer from, Pointer to) {
+ void explainRange(_Pointer from, _Pointer to) {
for (var pointer = from; pointer != to; ++pointer) {
explain(pointer);
}
diff --git a/pkgs/dart_model/lib/src/json_buffer/growable_map.dart b/pkgs/dart_model/lib/src/json_buffer/growable_map.dart
index 39229b0..06a4fae 100644
--- a/pkgs/dart_model/lib/src/json_buffer/growable_map.dart
+++ b/pkgs/dart_model/lib/src/json_buffer/growable_map.dart
@@ -38,19 +38,19 @@
/// somewhere in the buffer. Otherwise, it won't be reachable from the
/// root [map].
Map<String, V> createGrowableMap<V>() {
- explanations?.push('addGrowableMap');
+ _explanations?.push('addGrowableMap');
final pointer = _reserve(_pointerSize + _lengthSize);
// Initially a "growable map" is just a null pointer and zero size, so
// there is nothing to write.
- explanations?.pop();
+ _explanations?.pop();
return _readGrowableMap<V>(pointer);
}
- /// Returns the [Pointer] to [map].
+ /// Returns the [_Pointer] to [map].
///
/// The [map] must have been created in this buffer using
/// [createGrowableMap]. Otherwise, [UnsupportedError] is thrown.
- Pointer _pointerToGrowableMap(_GrowableMap<Object?> map) {
+ _Pointer _pointerToGrowableMap(_GrowableMap<Object?> map) {
_checkGrowableMapOwnership(map);
return map._pointer;
}
@@ -58,25 +58,25 @@
/// Throws if [map is backed by a different buffer to `this`.
void _checkGrowableMapOwnership(_GrowableMap map) {
if (map._buffer != this) {
- throw UnsupportedError('Maps created with `addGrowableMap` can only '
+ throw UnsupportedError('Maps created with `createGrowableMap` can only '
'be added to the JsonBufferBuilder instance that created them.');
}
}
/// Returns the [_GrowableMap] at [pointer].
- Map<String, V> _readGrowableMap<V>(Pointer pointer) {
+ Map<String, V> _readGrowableMap<V>(_Pointer pointer) {
return _GrowableMap<V>(this, pointer);
}
}
class _GrowableMap<V> with MapMixin<String, V>, _EntryMapMixin<String, V> {
final JsonBufferBuilder _buffer;
- final Pointer _pointer;
+ final _Pointer _pointer;
int _length;
- Pointer? _lastPointer;
+ _Pointer? _lastPointer;
_GrowableMap(this._buffer, this._pointer)
- : _length = _buffer.readLength(_pointer + _pointerSize);
+ : _length = _buffer._readLength(_pointer + _pointerSize);
@override
int get length => _length;
@@ -115,7 +115,7 @@
/// iterable.
@override
void operator []=(String key, V value) {
- explanations?.push('GrowableMap[]= $key $value');
+ _buffer._explanations?.push('GrowableMap[]= $key $value');
// If `_lastPointer` is not set yet, walk the map to find the end of it.
if (_lastPointer == null) {
@@ -140,7 +140,7 @@
// Update length.
++_length;
_buffer._writeLength(_pointer + _pointerSize, length, allowOverwrite: true);
- explanations?.pop();
+ _buffer._explanations?.pop();
}
@override
@@ -157,7 +157,7 @@
/// `Iterator` that reads a "growable map" in a [JsonBufferBuilder].
abstract class _GrowableMapIterator<T> implements Iterator<T> {
final JsonBufferBuilder _buffer;
- Pointer _pointer;
+ _Pointer _pointer;
_GrowableMapIterator(this._buffer, this._pointer);
@@ -165,13 +165,13 @@
T get current;
String get _currentKey =>
- _buffer.readString(_buffer.readPointer(_pointer + _pointerSize));
+ _buffer._readString(_buffer._readPointer(_pointer + _pointerSize));
Object? get _currentValue =>
_buffer._readAny(_pointer + _pointerSize + GrowableMaps._keySize);
@override
bool moveNext() {
- _pointer = _buffer.readPointer(_pointer);
+ _pointer = _buffer._readPointer(_pointer);
return _pointer != 0;
}
}
diff --git a/pkgs/dart_model/lib/src/json_buffer/iterables.dart b/pkgs/dart_model/lib/src/json_buffer/iterables.dart
index d85385e..21ef60a 100644
--- a/pkgs/dart_model/lib/src/json_buffer/iterables.dart
+++ b/pkgs/dart_model/lib/src/json_buffer/iterables.dart
@@ -5,7 +5,7 @@
part of 'json_buffer_builder.dart';
/// More efficient implementations than `MapMixin` for maps with efficient
-/// `entries`.
+/// `entries` and `length`.
mixin _EntryMapMixin<K, V> on Map<K, V> {
// `MapMixin` iterates keys then looks up each value.
//
@@ -16,6 +16,14 @@
action(entry.key, entry.value);
}
}
+
+ // `MapMixin` uses `keys.isEmpty`.
+ @override
+ bool get isEmpty => length == 0;
+
+// `MapMixin` uses `keys.isNotEmpty`.
+ @override
+ bool get isNotEmpty => length != 0;
}
/// An [Iterable] that uses the supplied function to create an [Iterator].
diff --git a/pkgs/dart_model/lib/src/json_buffer/json_buffer_builder.dart b/pkgs/dart_model/lib/src/json_buffer/json_buffer_builder.dart
index 9cd12e6..c42b85c 100644
--- a/pkgs/dart_model/lib/src/json_buffer/json_buffer_builder.dart
+++ b/pkgs/dart_model/lib/src/json_buffer/json_buffer_builder.dart
@@ -27,10 +27,16 @@
/// Whether writes are allowed.
final bool _allowWrites;
- final Map<TypedMapSchema, Pointer> _pointersBySchema = Map.identity();
- final Map<Pointer, TypedMapSchema> _schemasByPointer = {};
+ /// [_Pointer]s to [TypedMapSchema]s used by [_TypedMap]s, so that the
+ /// same schema is only written once.
+ final Map<TypedMapSchema, _Pointer> _pointersBySchema = Map.identity();
- final Map<String, Pointer> _pointersByString = {};
+ /// [TypedMapSchema] used by [_TypedMap]s by [_Pointer], so that the same
+ /// schema is only read once.
+ final Map<_Pointer, TypedMapSchema> _schemasByPointer = {};
+
+ /// [_Pointer]s to `String`s, so that the same `String` is only written once.
+ final Map<String, _Pointer> _pointersByString = {};
JsonBufferBuilder.deserialize(this._buffer)
: _allowWrites = false,
@@ -54,44 +60,44 @@
/// The number of bytes written.
int get length => _nextFree;
- /// Reads the value at [Pointer], which must have been written with
+ /// Reads the value at [_Pointer], which must have been written with
/// [_writeAny].
- Object? _readAny(Pointer pointer) {
- final type = readType(pointer);
+ Object? _readAny(_Pointer pointer) {
+ final type = _readType(pointer);
return _read(type, pointer + _typeSize);
}
- /// Reads the value of type [Type] at [Pointer].
- Object? _read(Type type, Pointer pointer) {
+ /// Reads the value of type [Type] at [_Pointer].
+ Object? _read(Type type, _Pointer pointer) {
switch (type) {
case Type.nil:
return null;
case Type.type:
- return readType(pointer);
+ return _readType(pointer);
case Type.pointer:
- return readPointer(pointer);
+ return _readPointer(pointer);
case Type.uint32:
- return readUint32(pointer);
+ return _readUint32(pointer);
case Type.boolean:
- return readBoolean(pointer);
+ return _readBoolean(pointer);
case Type.stringPointer:
- return readString(readPointer(pointer));
+ return _readString(_readPointer(pointer));
case Type.closedMapPointer:
- return readClosedMap(readPointer(pointer));
+ return _readClosedMap(_readPointer(pointer));
case Type.growableMapPointer:
- return _readGrowableMap<Object?>(readPointer(pointer));
+ return _readGrowableMap<Object?>(_readPointer(pointer));
case Type.typedMapPointer:
- return readTypedMap(readPointer(pointer));
+ return _readTypedMap(_readPointer(pointer));
}
}
/// Writes the type of [value] then writes the value using [_writeAnyOfType].
- void _writeAny(Pointer pointer, Object? value) {
- explanations?.push('_writeAny $pointer $value');
+ void _writeAny(_Pointer pointer, Object? value) {
+ _explanations?.push('_writeAny $pointer $value');
final type = Type._of(value);
_writeType(pointer, type);
_writeAnyOfType(type, pointer + _typeSize, value);
- explanations?.pop();
+ _explanations?.pop();
}
/// Writes [value] of type [type] to [pointer].
@@ -102,8 +108,8 @@
///
/// Otherwise, a pointer is written. It might be a pointer to an existing
/// value if there is one, or a new value may be written as well.
- void _writeAnyOfType(Type type, Pointer pointer, Object? value) {
- explanations?.push('_writeAnyOfType $type $pointer $value');
+ void _writeAnyOfType(Type type, _Pointer pointer, Object? value) {
+ _explanations?.push('_writeAnyOfType $type $pointer $value');
switch (type) {
case Type.nil:
// Nothing to write.
@@ -113,7 +119,7 @@
_writeType(pointer, value as Type);
case Type.pointer:
- _writePointer(pointer, value as Pointer);
+ _writePointer(pointer, value as _Pointer);
case Type.uint32:
_writeUint32(pointer, value as int);
@@ -134,95 +140,107 @@
case Type.typedMapPointer:
_writePointer(pointer, _pointerToTypedMap(value as _TypedMap));
}
- explanations?.pop();
+ _explanations?.pop();
}
- /// Returns a [Pointer] to the `String` [value].
+ /// Returns a [_Pointer] to the `String` [string].
///
- /// Returns the [Pointer] of an existing equal `String` if there is one,
+ /// Returns the [_Pointer] of an existing equal `String` if there is one,
/// otherwise adds it.
- Pointer _pointerToString(String value) =>
- _pointersByString[value] ??= _addString(value);
+ _Pointer _pointerToString(String string) =>
+ _pointersByString[string] ??= _addString(string);
- Pointer _addString(String value) {
- explanations?.push('__pointerToString $value');
- final bytes = utf8.encode(value);
+ /// Adds the `String` [string], returns a [_Pointer] to it.
+ _Pointer _addString(String string) {
+ _explanations?.push('__pointerToString $string');
+ final bytes = utf8.encode(string);
final length = bytes.length;
final pointer = _reserve(_lengthSize + length);
_writeLength(pointer, length);
- _setRange(pointer + 4, pointer + 4 + length, bytes);
- explanations?.pop();
+ _setRange(pointer + _lengthSize, pointer + _lengthSize + length, bytes);
+ _explanations?.pop();
return pointer;
}
- void _writeType(Pointer pointer, Type value) {
- explanations?.push('_writeType $value');
- _setByte(pointer, value.index);
- explanations?.pop();
+ /// Reads the String at [pointer].
+ String _readString(_Pointer pointer) {
+ final length = _readLength(pointer);
+ return utf8.decode(
+ _buffer.sublist(pointer + _lengthSize, pointer + _lengthSize + length));
}
- Type readType(Pointer pointer) {
+ /// Writes [type] at [pointer].
+ void _writeType(_Pointer pointer, Type type) {
+ _explanations?.push('_writeType $type');
+ _setByte(pointer, type.index);
+ _explanations?.pop();
+ }
+
+ /// Reads the `Type` at [pointer].
+ Type _readType(_Pointer pointer) {
return Type.values[_buffer[pointer]];
}
- void _writeLength(Pointer pointer, Pointer value,
+ /// Writes [length] at [pointer].
+ void _writeLength(_Pointer pointer, int length,
{bool allowOverwrite = false}) {
- explanations?.push('_writeLength $value');
- __writeUint32(pointer, value, allowOverwrite: allowOverwrite);
- explanations?.pop();
+ _explanations?.push('_writeLength $length');
+ __writeUint32(pointer, length, allowOverwrite: allowOverwrite);
+ _explanations?.pop();
}
- /// Adds a new pointer pointing to [pointer], returns it.
- Pointer _addPointerTo(Pointer pointer) {
- explanations?.push('_addPointerToPointer $pointer');
- final result = _reserve(4);
+ /// Reads the length at [_Pointer].
+ _Pointer _readLength(_Pointer pointer) => _readUint32(pointer);
+
+ /// Adds [pointer] to the buffer, returns a new [_Pointer] to it.
+ _Pointer _addPointerTo(_Pointer pointer) {
+ _explanations?.push('_addPointerToPointer $pointer');
+ final result = _reserve(_pointerSize);
_writePointer(result, pointer);
- explanations?.pop();
+ _explanations?.pop();
return result;
}
- void _writePointer(Pointer pointer, Pointer value) {
- explanations?.push('_writePointer $value');
- __writeUint32(pointer, value);
- explanations?.pop();
+ /// Writes [pointerValue] at [pointer].
+ void _writePointer(_Pointer pointer, _Pointer pointerValue) {
+ _explanations?.push('_writePointer $pointerValue');
+ __writeUint32(pointer, pointerValue);
+ _explanations?.pop();
}
- void _writeUint32(Pointer pointer, int value) {
- explanations?.push('_writeUint32 $value');
+ /// Reads the [_Pointer] at [_Pointer].
+ _Pointer _readPointer(_Pointer pointer) => _readUint32(pointer);
+
+ /// Writes [value] at [pointer].
+ void _writeUint32(_Pointer pointer, int value) {
+ _explanations?.push('_writeUint32 $value');
__writeUint32(pointer, value);
- explanations?.pop();
+ _explanations?.pop();
}
- void __writeUint32(Pointer pointer, int value,
+ void __writeUint32(_Pointer pointer, int value,
{bool allowOverwrite = false}) {
_setFourBytes(pointer, value & 0xff, (value >> 8) & 0xff,
(value >> 16) & 0xff, (value >> 24) & 0xff,
allowOverwrite: allowOverwrite);
}
- void _writeBoolean(Pointer pointer, bool value) {
- explanations?.push('_writeBoolean $value');
- _setByte(pointer, value ? 1 : 0);
- explanations?.pop();
- }
-
- /// Reads the length at [Pointer].
- Pointer readLength(Pointer pointer) => _readUint32(pointer);
-
- /// Reads the [Pointer] at [Pointer].
- Pointer readPointer(Pointer pointer) => _readUint32(pointer);
-
- /// Reads the uint32 at [Pointer].
- int readUint32(Pointer pointer) => _readUint32(pointer);
-
- int _readUint32(Pointer pointer) =>
+ /// Reads the uint32 at [_Pointer].
+ int _readUint32(_Pointer pointer) =>
_buffer[pointer] +
(_buffer[pointer + 1] << 8) +
(_buffer[pointer + 2] << 16) +
(_buffer[pointer + 3] << 24);
- /// Reads the boolean at [Pointer].
- bool readBoolean(Pointer pointer) {
+ /// Writes [boolean] at [pointer].
+ void _writeBoolean(_Pointer pointer, bool boolean) {
+ _explanations?.push('_writeBoolean $boolean');
+ _setByte(pointer, boolean ? 1 : 0);
+ _explanations?.pop();
+ }
+
+ /// Reads the `bool` at [_Pointer].
+ bool _readBoolean(_Pointer pointer) {
switch (_buffer[pointer]) {
case 0:
return false;
@@ -233,34 +251,26 @@
}
}
- /// Reads the String at [Pointer].
- String readString(Pointer pointer) {
- final length = readLength(pointer);
- return utf8.decode(
- _buffer.sublist(pointer + _lengthSize, pointer + _lengthSize + length));
- }
-
- void _setBit(Pointer pointer, int bitIndex, bool value) {
- _checkAllowWrites();
- explanations?.explain(pointer, allowOverwrite: true);
- if (value) {
- _buffer[pointer] |= 1 << bitIndex;
- } else {
- _buffer[pointer] &= 0xff ^ (1 << bitIndex);
- }
- }
-
- bool readBit(Pointer pointer, int bitIndex) {
+ /// Reads the bit at [pointer], index [bitIndex].
+ bool _readBit(_Pointer pointer, int bitIndex) {
return ((_buffer[pointer] >> bitIndex) & 1) == 1;
}
- void _setByte(Pointer pointer, int uint8, {bool allowOverwrite = false}) {
+ /// Sets the byte at [pointer] to [uint8].
+ ///
+ /// If [_explanations] is being used, [allowOverwrite] controls whether
+ /// multiple writes to the same byte will be allowed or will throw.
+ void _setByte(_Pointer pointer, int uint8, {bool allowOverwrite = false}) {
_checkAllowWrites();
- explanations?.explain(pointer, allowOverwrite: allowOverwrite);
+ _explanations?.explain(pointer, allowOverwrite: allowOverwrite);
_buffer[pointer] = uint8;
}
- void _setFourBytes(Pointer pointer, int b1, int b2, int b3, int b4,
+ /// Sets the four bytes at [pointer] to [b1] [b2] [b3] [b4].
+ ///
+ /// If [_explanations] is being used, [allowOverwrite] controls whether
+ /// multiple writes to the same byte will be allowed or will throw.
+ void _setFourBytes(_Pointer pointer, int b1, int b2, int b3, int b4,
{bool allowOverwrite = false}) {
_setByte(pointer, b1, allowOverwrite: allowOverwrite);
_setByte(pointer + 1, b2, allowOverwrite: allowOverwrite);
@@ -268,9 +278,10 @@
_setByte(pointer + 3, b4, allowOverwrite: allowOverwrite);
}
- void _setRange(Pointer from, Pointer to, Uint8List bytes) {
+ /// Sets the rang of bytes [from] until [to] to [bytes].
+ void _setRange(_Pointer from, _Pointer to, Uint8List bytes) {
_checkAllowWrites();
- explanations?.explainRange(from, to);
+ _explanations?.explainRange(from, to);
_buffer.setRange(from, to, bytes);
}
@@ -278,8 +289,8 @@
///
/// Increases `_nextFree` accordingly. Expands the buffer if necessary.
///
- /// Returns a [Pointer] to the reserved space.
- Pointer _reserve(int bytes) {
+ /// Returns a [_Pointer] to the reserved space.
+ _Pointer _reserve(int bytes) {
_checkAllowWrites();
final result = _nextFree;
_nextFree += bytes;
@@ -297,17 +308,27 @@
_buffer.setRange(0, oldBuffer.length, oldBuffer);
}
+ /// Throws if writes are not allowed.
void _checkAllowWrites() {
if (!_allowWrites) throw StateError('This JsonBufferBuilder is read-only.');
}
+ /// Explanations of each byte in the buffer.
+ ///
+ /// Run tests with `dart -Ddebug_json_buffer=true test -c source` to enable.
+ ///
+ /// Then, [JsonBufferBuilder#toString] prints additional information for
+ /// each byte.
+ final _Explanations? _explanations =
+ const bool.fromEnvironment('debug_json_buffer') ? _Explanations() : null;
+
@override
String toString() {
- if (explanations == null) return 'JsonBufferBuilder($_nextFree, $_buffer)';
+ if (_explanations == null) return 'JsonBufferBuilder($_nextFree, $_buffer)';
final result = StringBuffer('JsonBufferBuilder($_nextFree):\n');
for (var i = 0; i != _nextFree; ++i) {
final value = _buffer[i];
- final explanation = explanations!._explanationsByPointer[i];
+ final explanation = _explanations._explanationsByPointer[i];
if (explanation == null) {
result.writeln('$i: $value');
} else {
diff --git a/pkgs/dart_model/lib/src/json_buffer/type.dart b/pkgs/dart_model/lib/src/json_buffer/type.dart
index 2ae626b..a7c3ece 100644
--- a/pkgs/dart_model/lib/src/json_buffer/type.dart
+++ b/pkgs/dart_model/lib/src/json_buffer/type.dart
@@ -18,8 +18,10 @@
/// TODO(davidmorgan): this matters for performance; optimize.
const int _initialBufferSize = 32;
-typedef Pointer = int;
+/// A pointer into the buffer.
+typedef _Pointer = int;
+/// The type of a value in the buffer.
enum Type {
nil,
type,
@@ -31,6 +33,7 @@
growableMapPointer,
typedMapPointer;
+ /// Returns the [Type] of [value], or throws if it is not a supported type.
static Type _of(Object? value) {
switch (value) {
case Null():
@@ -54,8 +57,8 @@
'Unsupported type: ${value.runtimeType}, value: $value');
}
- /// Returns size in bytes.
- int get sizeInBytes {
+ /// The size in bytes of value of this type.
+ int get _sizeInBytes {
switch (this) {
case nil:
return 0;
diff --git a/pkgs/dart_model/lib/src/json_buffer/typed_map.dart b/pkgs/dart_model/lib/src/json_buffer/typed_map.dart
index 2233f78..cd51166 100644
--- a/pkgs/dart_model/lib/src/json_buffer/typed_map.dart
+++ b/pkgs/dart_model/lib/src/json_buffer/typed_map.dart
@@ -4,23 +4,37 @@
part of 'json_buffer_builder.dart';
+/// Schema of a "typed map": a map with known keys and their value types.
class TypedMapSchema {
+ /// The `String` keys of a "typed map" using this schema.
final List<String> _keys;
- final List<Type> _valueTypes;
- final List<int> _offsets;
- final bool isAllBooleans;
+ /// The value types of the fields in a "typed map" using this schema.
+ final List<Type> _valueTypes;
+
+ /// Whether all the fields are of type [Type.boolean].
+ final bool _isAllBooleans;
+
+ /// The number of bytes needed for a bit vector of which fields are present.
+ final int _fieldSetSize;
+
+ /// The byte size of all values if bools are not packed to bit vectors.
+ final int _valueSizeAsBytes;
+
+ /// Schema with the specified [fieldTypes].
+ ///
+ /// Ordering is important: when a "typed map" is instantiated the values are
+ /// passed in the same order that the fields are specified here.
TypedMapSchema(Map<String, Type> fieldTypes)
: this._(fieldTypes.keys.toList(), fieldTypes.values.toList());
TypedMapSchema._(this._keys, this._valueTypes)
- : _offsets = List<int>.filled(_keys.length + 1, 0),
- isAllBooleans = _valueTypes.every((t) => t == Type.boolean) {
- for (var i = 1; i != _valueTypes.length + 1; ++i) {
- _offsets[i] = _offsets[i - 1] + _valueTypes[i - 1].sizeInBytes;
- }
- }
+ : _isAllBooleans = _valueTypes.every((t) => t == Type.boolean),
+ _fieldSetSize = (_keys.length + 7) ~/ 8,
+ _valueSizeAsBytes =
+ _valueTypes.map((t) => t._sizeInBytes).fold(0, (a, b) => a + b);
+ /// The schema field names and value types as a `Map`.
Map<String, Type> toMap() => {
for (var i = 0; i != _keys.length; ++i) _keys[i]: _valueTypes[i],
};
@@ -37,23 +51,76 @@
int get hashCode =>
throw UnsupportedError('TypedMapSchema should be compared by identity.');
+ /// The number of fields.
int get length => _keys.length;
- int offsetAtIndex(int i) =>
- isAllBooleans ? throw StateError('TypedMap is all bools!') : _offsets[i];
+ /// The type of the field at index [i].
+ Type _typeAtIndex(int i) => _isAllBooleans ? Type.boolean : _valueTypes[i];
- Type typeAtIndex(int i) => isAllBooleans ? Type.boolean : _valueTypes[i];
+ /// The number of bytes needed to store all the values.
+ ///
+ /// If [_isAllBooleans] then this is the number of bytes needed for a bit
+ /// vector, otherwise it is the sum of [Type#_sizeInBytes] for all values.
+ int get _filledValueSize =>
+ _isAllBooleans ? _fieldSetSize : _valueSizeAsBytes;
- int get fieldSetSize => (length + 7) ~/ 8;
-
- int get valueSize => isAllBooleans ? fieldSetSize : _offsets.last;
+ /// The number of bytes needed to store the specified values.
+ ///
+ /// If [_isAllBooleans] then this is the number of bytes needed for a bit
+ /// vector of the present values, otherwise it is the sum of
+ /// [Type#_sizeInBytes] for all present values.
+ ///
+ /// Additionally returns a [bool]: whether the map is filled.
+ (int, bool) _valueSizeOf(
+ [Object? v0,
+ Object? v1,
+ Object? v2,
+ Object? v3,
+ Object? v4,
+ Object? v5,
+ Object? v6,
+ Object? v7]) {
+ if (_isAllBooleans) {
+ // All fields take up one bit, count then compute how many bytes.
+ var bits = 0;
+ if (v0 != null) ++bits;
+ if (v1 != null) ++bits;
+ if (v2 != null) ++bits;
+ if (v3 != null) ++bits;
+ if (v4 != null) ++bits;
+ if (v5 != null) ++bits;
+ if (v6 != null) ++bits;
+ if (v7 != null) ++bits;
+ return ((bits + 7) ~/ 8, bits == length);
+ } else {
+ // Sum the sizes of present values.
+ var result = 0;
+ if (v0 != null) result += _valueTypes[0]._sizeInBytes;
+ if (v1 != null) result += _valueTypes[1]._sizeInBytes;
+ if (v2 != null) result += _valueTypes[2]._sizeInBytes;
+ if (v3 != null) result += _valueTypes[3]._sizeInBytes;
+ if (v4 != null) result += _valueTypes[4]._sizeInBytes;
+ if (v5 != null) result += _valueTypes[5]._sizeInBytes;
+ if (v6 != null) result += _valueTypes[6]._sizeInBytes;
+ if (v7 != null) result += _valueTypes[7]._sizeInBytes;
+ return (result, result == _filledValueSize);
+ }
+ }
@override
- String toString() => 'TypedMapSchema$_keys$_valueTypes';
+ String toString() => 'TypedMapSchema${toMap()}';
}
+/// Methods for writing and reading "typed maps".
+///
+/// A "typed map" is a `Map<String, Object?>` in a byte buffer that has a know
+/// [TypedMapSchema] specify its keys and value types. In addition, all keys
+/// and values are known at the time of writing.
+///
+/// Values are optional; that is, they can be `null`.
extension TypedMaps on JsonBufferBuilder {
- /// Creates and fills a "typed map".
+ /// Creates and fills a "typed map" with keys and value types specified in
+ /// [schema].
///
/// It is linked to the `JsonBufferBuilder` that creates it: it can be added
/// to any collection in the same `JsonBufferBuilder` without copying.
@@ -61,6 +128,12 @@
///
/// The returned value must be separately added to the buffer. Otherwise,
/// the buffer contains data that is not reachable from the root [map].
+ ///
+ /// TODO(davidmorgan): benchmarking suggested that specialized
+ /// implementations of this method per schema size (createTypedMap1,
+ /// createdTypedMap2, etc) are not faster in the JIT VM but they are about
+ /// 5-10% faster in the AOT VM, consider adding specialized methods. This
+ /// will presumably matter more when if larger schemas are supported.
Map<String, Object?> createTypedMap(TypedMapSchema schema,
[Object? v0,
Object? v1,
@@ -70,114 +143,89 @@
Object? v5,
Object? v6,
Object? v7]) {
- explanations?.push('addTypedMap $schema');
+ _explanations?.push('addTypedMap $schema');
- final filled = switch (schema.length) {
- 0 => true,
- 1 => v0 != null,
- 2 => v0 != null && v1 != null,
- 3 => v0 != null && v1 != null && v2 != null,
- 4 => v0 != null && v1 != null && v2 != null && v3 != null,
- 5 => v0 != null && v1 != null && v2 != null && v3 != null && v4 != null,
- 6 => v0 != null &&
- v1 != null &&
- v2 != null &&
- v3 != null &&
- v4 != null &&
- v5 != null,
- 7 => v0 != null &&
- v1 != null &&
- v2 != null &&
- v3 != null &&
- v4 != null &&
- v5 != null &&
- v6 != null,
- 8 => v0 != null &&
- v1 != null &&
- v2 != null &&
- v3 != null &&
- v4 != null &&
- v5 != null &&
- v6 != null &&
- v7 != null,
- _ => throw UnsupportedError('Too long: ${schema.length}')
- };
+ // Compute how much space the values need, and whether the map is filled.
+ // If the map is filled this is marked with the high bit of the schema
+ // pointer, then the field set is omitted.
+ final (valuesSize, filled) =
+ schema._valueSizeOf(v0, v1, v2, v3, v4, v5, v6, v7);
+ // Layout is: pointer to schema, field set (unless filled!), values.
+ final pointer = _reserve(
+ _pointerSize + (filled ? 0 : schema._fieldSetSize) + valuesSize);
+
+ // Write the pointer to schema, setting the high bit if the map is filled.
var schemaPointer = _pointersBySchema[schema] ??=
_addPointerTo(_addClosedMap(schema.toMap()));
if (filled) schemaPointer |= 0x80000000;
-
- final size = filled
- ? schema.valueSize
- : (v0 == null ? 0 : schema.typeAtIndex(0).sizeInBytes) +
- (v1 == null ? 0 : schema.typeAtIndex(1).sizeInBytes) +
- (v2 == null ? 0 : schema.typeAtIndex(2).sizeInBytes) +
- (v3 == null ? 0 : schema.typeAtIndex(3).sizeInBytes) +
- (v4 == null ? 0 : schema.typeAtIndex(4).sizeInBytes) +
- (v5 == null ? 0 : schema.typeAtIndex(5).sizeInBytes) +
- (v6 == null ? 0 : schema.typeAtIndex(6).sizeInBytes) +
- (v7 == null ? 0 : schema.typeAtIndex(7).sizeInBytes);
-
- final pointer =
- _reserve(_pointerSize + (filled ? 0 : schema.fieldSetSize) + size);
_writePointer(pointer, schemaPointer);
+ // If not filled, write the field set: a bit vector indicating which fields
+ // are present.
if (!filled) {
_setByte(
pointer + _pointerSize,
- (v0 == null ? 0 : 1) +
- (v1 == null ? 0 : 2) +
- (v2 == null ? 0 : 4) +
- (v3 == null ? 0 : 8) +
- (v4 == null ? 0 : 16) +
- (v5 == null ? 0 : 32) +
- (v6 == null ? 0 : 64) +
- (v7 == null ? 0 : 128));
+ (v0 == null ? 0 : 0x01) +
+ (v1 == null ? 0 : 0x02) +
+ (v2 == null ? 0 : 0x04) +
+ (v3 == null ? 0 : 0x08) +
+ (v4 == null ? 0 : 0x10) +
+ (v5 == null ? 0 : 0x20) +
+ (v6 == null ? 0 : 0x40) +
+ (v7 == null ? 0 : 0x80));
}
- var offset = 0;
-
- void addValue(int index, Object? value) {
- explanations?.push('addValue $index $value');
-
- if (schema.isAllBooleans) {
- final valuePointer = pointer +
- _pointerSize +
- (filled ? 0 : schema.fieldSetSize) +
- (offset ~/ 8);
- final bitIndex = offset % 8;
- _setBit(valuePointer, bitIndex, value as bool);
- offset++;
- } else {
- final valuePointer = pointer +
- _pointerSize +
- (filled ? 0 : schema.fieldSetSize) +
- offset;
- final valueType = schema.typeAtIndex(index);
- _writeAnyOfType(valueType, valuePointer, value);
- offset += valueType.sizeInBytes;
+ // Write the values.
+ var valuePointer =
+ pointer + _pointerSize + (filled ? 0 : schema._fieldSetSize);
+ if (schema._isAllBooleans) {
+ // If all booleans, pack into a byte bit vector.
+ var byte = 0;
+ var bitmask = 0x01;
+ void addBit(bool bit) {
+ if (bit) byte += bitmask;
+ bitmask <<= 1;
}
- explanations?.pop();
+
+ if (v0 != null) addBit(v0 == true);
+ if (v1 != null) addBit(v1 == true);
+ if (v2 != null) addBit(v2 == true);
+ if (v3 != null) addBit(v3 == true);
+ if (v4 != null) addBit(v4 == true);
+ if (v5 != null) addBit(v5 == true);
+ if (v6 != null) addBit(v6 == true);
+ if (v7 != null) addBit(v7 == true);
+ _setByte(valuePointer, byte);
+ } else {
+ // If not all booleans, write only present values according to their
+ // size in bytes.
+ void addValue(int index, Object? value) {
+ _explanations?.push('addValue $index $value');
+ final valueType = schema._typeAtIndex(index);
+ _writeAnyOfType(valueType, valuePointer, value);
+ valuePointer += valueType._sizeInBytes;
+ _explanations?.pop();
+ }
+
+ if (v0 != null) addValue(0, v0);
+ if (v1 != null) addValue(1, v1);
+ if (v2 != null) addValue(2, v2);
+ if (v3 != null) addValue(3, v3);
+ if (v4 != null) addValue(4, v4);
+ if (v5 != null) addValue(5, v5);
+ if (v6 != null) addValue(6, v6);
+ if (v7 != null) addValue(7, v7);
}
- if (v0 != null) addValue(0, v0);
- if (v1 != null) addValue(1, v1);
- if (v2 != null) addValue(2, v2);
- if (v3 != null) addValue(3, v3);
- if (v4 != null) addValue(4, v4);
- if (v5 != null) addValue(5, v5);
- if (v6 != null) addValue(6, v6);
- if (v7 != null) addValue(7, v7);
-
- explanations?.pop();
+ _explanations?.pop();
return _TypedMap(this, pointer);
}
- /// Returns the [Pointer] to [map].
+ /// Returns the [_Pointer] to [map].
///
- /// The [map] must have been created in this buffer using
- /// [createTypedMap].
- Pointer _pointerToTypedMap(_TypedMap map) {
+ /// The [map] must have been created in this buffer using [createTypedMap].
+ _Pointer _pointerToTypedMap(_TypedMap map) {
_checkTypedMapOwnership(map);
return map._pointer;
}
@@ -190,7 +238,8 @@
}
}
- Map<String, Object?> readTypedMap(Pointer pointer) {
+ /// Returns the [_TypedMap] at [pointer].
+ Map<String, Object?> _readTypedMap(_Pointer pointer) {
return _TypedMap(this, pointer);
}
}
@@ -199,21 +248,28 @@
with MapMixin<String, Object?>, _EntryMapMixin
implements Map<String, Object?> {
final JsonBufferBuilder _buffer;
- final Pointer _pointer;
+ final _Pointer _pointer;
// If a `TypedMap` is created then immediately added to another `Map` then
// these values are never needed, just the `_pointer`. Use `late` so they are
// only computed if needed.
- late final Pointer _schemaPointer =
- _buffer.readPointer(_pointer) & 0x7fffffff;
+
+ late final _Pointer _schemaPointer =
+ // The high byte of the schema pointer indicates "filled", omit it.
+ _buffer._readPointer(_pointer) & 0x7fffffff;
+
+ /// The schema of this "typed map" giving its field names and types.
late final TypedMapSchema _schema =
_buffer._schemasByPointer[_schemaPointer] ??= TypedMapSchema(
- _buffer.readClosedMap(_buffer.readPointer(_schemaPointer)).cast());
- late final bool filled = (_buffer.readPointer(_pointer) & 0x80000000) != 0;
+ _buffer._readClosedMap(_buffer._readPointer(_schemaPointer)).cast());
+
+ /// Whether all fields are present, meaning no explicit field set was written.
+ late final bool filled = (_buffer._readPointer(_pointer) & 0x80000000) != 0;
_TypedMap(this._buffer, this._pointer);
- bool hasField(int index) {
+ /// Whether the field at [index] is present.
+ bool _hasField(int index) {
if (index < 0 || index >= _schema.length) {
throw RangeError.value(
index, 'index', 'Is out of range, length: ${_schema.length}.');
@@ -221,15 +277,12 @@
if (filled) return true;
final byte = index ~/ 8;
final bit = index % 8;
- return _buffer.readBit(_pointer + _pointerSize + byte, bit);
+ return _buffer._readBit(_pointer + _pointerSize + byte, bit);
}
@override
Object? operator [](Object? key) {
final iterator = entries.iterator;
- // TODO(davidmorgan): for small maps this is probably already efficient
- // enough. Do something better for large maps, for example sorting keys
- // and binary search?
while (iterator.moveNext()) {
if (iterator.current.key == key) return iterator.current.value;
}
@@ -240,33 +293,26 @@
@override
late final int length = _computeLength();
+ /// Computes length from present fields.
int _computeLength() {
if (filled) return _schema.length;
var result = 0;
for (var i = 0; i != _schema.length; ++i) {
- if (hasField(i)) ++result;
+ if (_hasField(i)) ++result;
}
return result;
}
- // For performance, `MapMixin` uses `keys.isEmpty`.
- @override
- bool get isEmpty => filled ? _schema.length == 0 : length == 0;
-
-// For performance, `MapMixin` uses `keys.isNotEmpty`.
- @override
- bool get isNotEmpty => !isEmpty;
-
@override
late final Iterable<String> keys = _IteratorFunctionIterable<String>(
- _schema.isAllBooleans
+ _schema._isAllBooleans
? () => _AllBoolsTypedMapKeyIterator(this)
: () => _PartialTypedMapKeyIterator(this),
length: length);
@override
late final Iterable<Object?> values = _IteratorFunctionIterable<Object?>(
- _schema.isAllBooleans
+ _schema._isAllBooleans
? () => _AllBoolsTypedMapValueIterator(this)
: () => _PartialTypedMapValueIterator(this),
length: length);
@@ -274,42 +320,49 @@
@override
late Iterable<MapEntry<String, Object?>> entries =
_IteratorFunctionIterable<MapEntry<String, Object?>>(
- _schema.isAllBooleans
+ _schema._isAllBooleans
? () => _AllBoolsTypedMapEntryIterator(this)
: () => _PartialTypedMapEntryIterator(this),
length: length);
@override
void operator []=(String key, Object? value) {
- throw UnsupportedError('JsonBuffer is readonly.');
+ throw UnsupportedError(
+ 'This JsonBufferBuilder map is read-only, see "createGrowableMap".');
}
@override
Object? remove(Object? key) {
- throw UnsupportedError('JsonBuffer is readonly.');
+ throw UnsupportedError(
+ 'This JsonBufferBuilder map is read-only, see "createGrowableMap".');
}
@override
void clear() {
- throw UnsupportedError('JsonBuffer is readonly.');
+ throw UnsupportedError(
+ 'This JsonBufferBuilder map is read-only, see "createGrowableMap".');
}
}
-/// `Iterator` that reads a `Map` in a [JsonBufferBuilder].
+/// `Iterator` that reads a "typed map" in a [JsonBufferBuilder].
abstract class _PartialTypedMapIterator<T> implements Iterator<T> {
final _TypedMap _map;
final JsonBufferBuilder _buffer;
final TypedMapSchema _schema;
- final Pointer _valuesPointer;
- int _offset = -1;
+ final _Pointer _valuesPointer;
+
+ /// The current field's index in the schema.
int _index = -1;
+ /// The current byte offset from [_valuesPointer.]
+ int _offset = -1;
+
_PartialTypedMapIterator(this._map)
: _buffer = _map._buffer,
_schema = _map._schema,
_valuesPointer = _map._pointer +
_pointerSize +
- (_map.filled ? 0 : _map._schema.fieldSetSize);
+ (_map.filled ? 0 : _map._schema._fieldSetSize);
@override
T get current;
@@ -320,11 +373,20 @@
@override
bool moveNext() {
- _offset += _index == -1 ? 1 : _schema._valueTypes[_index].sizeInBytes;
+ // Update the offset.
+ //
+ // If before the first byte, move to it.
+ //
+ // Otherwise, the iterator is at a present value: advance by its size in bytes.
+ _offset += _index == -1 ? 1 : _schema._valueTypes[_index]._sizeInBytes;
+
+ // Update the field index.
do {
++_index;
+ // If index is now outside the schema, iteration is finished.
if (_index == _schema.length) return false;
- } while (!_map.hasField(_index));
+ // Skip missing fields.
+ } while (!_map._hasField(_index));
return true;
}
}
@@ -351,14 +413,21 @@
MapEntry<String, Object?> get current => MapEntry(_currentKey, _currentValue);
}
-/// `Iterator` that reads a `Map` in a [JsonBufferBuilder].
+/// `Iterator` that reads a "typed map" in a [JsonBufferBuilder] with all
+/// values of type [Type.boolean].
abstract class _AllBoolsTypedMapIterator<T> implements Iterator<T> {
final _TypedMap _map;
final JsonBufferBuilder _buffer;
final TypedMapSchema _schema;
- final Pointer _valuesPointer;
+ final _Pointer _valuesPointer;
+
+ /// The current field's index in the schema.
int _index = -1;
+
+ /// The byte offset from [_valuesPointer].
int _intOffset = -1;
+
+ /// The number of the current bit in the current byte.
int _bitOffset = 7;
_AllBoolsTypedMapIterator(this._map)
@@ -366,26 +435,31 @@
_schema = _map._schema,
_valuesPointer = _map._pointer +
_pointerSize +
- (_map.filled ? 0 : _map._schema.fieldSetSize);
+ (_map.filled ? 0 : _map._schema._fieldSetSize);
@override
T get current;
String get _currentKey => _schema._keys[_index];
Object? get _currentValue =>
- _buffer.readBit(_valuesPointer + _intOffset, _bitOffset);
+ _buffer._readBit(_valuesPointer + _intOffset, _bitOffset);
@override
bool moveNext() {
+ // Update the offset: advance by one bit.
++_bitOffset;
if (_bitOffset == 8) {
_bitOffset = 0;
++_intOffset;
}
+
+ // Update the field index.
do {
++_index;
+ // If index is now outside the schema, iteration is finished.
if (_index == _map._schema.length) return false;
- } while (!_map.hasField(_index));
+ // Skip missing fields.
+ } while (!_map._hasField(_index));
return true;
}
}
diff --git a/pkgs/dart_model/test/json_buffer/closed_map_test.dart b/pkgs/dart_model/test/json_buffer/closed_map_test.dart
index d7f10ff..faa6633 100644
--- a/pkgs/dart_model/test/json_buffer/closed_map_test.dart
+++ b/pkgs/dart_model/test/json_buffer/closed_map_test.dart
@@ -13,11 +13,11 @@
setUp(() {
builder = JsonBufferBuilder();
- explanations = Explanations();
});
tearDown(() {
- print(builder);
+ printOnFailure('Try: dart -Ddebug_json_buffer=true test -c source');
+ printOnFailure(builder.toString());
});
test('simple write and read', () {
diff --git a/pkgs/dart_model/test/json_buffer/growable_map_test.dart b/pkgs/dart_model/test/json_buffer/growable_map_test.dart
index 42e41f5..932e701 100644
--- a/pkgs/dart_model/test/json_buffer/growable_map_test.dart
+++ b/pkgs/dart_model/test/json_buffer/growable_map_test.dart
@@ -13,11 +13,11 @@
setUp(() {
builder = JsonBufferBuilder();
- explanations = Explanations();
});
tearDown(() {
- print(builder);
+ printOnFailure('Try: dart -Ddebug_json_buffer=true test -c source');
+ printOnFailure(builder.toString());
});
test('can be written and read if empty', () {
diff --git a/pkgs/dart_model/test/json_buffer/json_buffer_builder_test.dart b/pkgs/dart_model/test/json_buffer/json_buffer_builder_test.dart
index 981fb0f..78d1e52 100644
--- a/pkgs/dart_model/test/json_buffer/json_buffer_builder_test.dart
+++ b/pkgs/dart_model/test/json_buffer/json_buffer_builder_test.dart
@@ -11,11 +11,11 @@
setUp(() {
builder = JsonBufferBuilder();
- explanations = Explanations();
});
tearDown(() {
- print(builder);
+ printOnFailure('Try: dart -Ddebug_json_buffer=true test -c source');
+ printOnFailure(builder.toString());
});
test('writes and reads supported types', () {
diff --git a/pkgs/dart_model/test/json_buffer/typed_map_test.dart b/pkgs/dart_model/test/json_buffer/typed_map_test.dart
index d3d53f0..d65ba1f 100644
--- a/pkgs/dart_model/test/json_buffer/typed_map_test.dart
+++ b/pkgs/dart_model/test/json_buffer/typed_map_test.dart
@@ -13,11 +13,11 @@
setUp(() {
builder = JsonBufferBuilder();
- explanations = Explanations();
});
tearDown(() {
- print(builder);
+ printOnFailure('Try: dart -Ddebug_json_buffer=true test -c source');
+ printOnFailure(builder.toString());
});
test('with some values missing can be written and read', () {
@@ -29,7 +29,6 @@
'missing2': Type.stringPointer
});
final map = builder.createTypedMap(schema, 'aa', true, null, 12345, null);
- print(map);
expectFullyEquivalentMaps(map, {'a': 'aa', 'b': true, 'c': 12345});
});
@@ -123,6 +122,33 @@
expect(length2 - length1, 4 + 1);
});
+ test('if all fields are bools or nulls they are packed into bits', () {
+ final schema = TypedMapSchema({
+ 'a': Type.boolean,
+ 'b': Type.boolean,
+ 'c': Type.boolean,
+ 'd': Type.boolean,
+ 'e': Type.boolean,
+ 'f': Type.boolean,
+ 'g': Type.boolean,
+ 'h': Type.boolean,
+ });
+
+ // Write once so schema is written.
+ builder.createTypedMap(
+ schema, null, false, true, false, true, false, true, false);
+
+ // Check length of write with already-written schema.
+ final length1 = builder.length;
+ builder.createTypedMap(
+ schema, true, null, true, false, true, false, true, false);
+ final length2 = builder.length;
+
+ // Size should be schema pointer, one byte field set, one byte for seven
+ // bools.
+ expect(length2 - length1, 4 + 1 + 1);
+ });
+
test('if not filled skipped fields save space', () {
final schema = TypedMapSchema({
'a': Type.uint32,