Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions dart/lib/flat_buffers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,42 @@ class ListReader<E> extends Reader<List<E>> {
}
}

/// The reader of lists of objects. Lazy by default - see [lazy].
class UnionListReader<E> extends Reader<List<E?>> {
final Reader<E>? Function(int index) _getElementReader;

/// Enables lazy reading of the list
///
/// If true, the returned unmodifiable list lazily reads objects on access.
/// Therefore, the underlying buffer must not change while accessing the list.
///
/// If false, reads the whole list immediately on access.
final bool lazy;

const UnionListReader(this._getElementReader, {this.lazy = true});

@override
@pragma('vm:prefer-inline')
int get size => _sizeofUint32;

@override
List<E?> read(BufferContext bc, int offset) {
final listOffset = bc.derefObject(offset);
return lazy
? _FbUnionList<E>(_getElementReader, bc, listOffset)
: List<E?>.generate(
bc.buffer.getUint32(listOffset, Endian.little),
(int index) {
final reader = _getElementReader(index);
if (reader == null) return null;
int offset = listOffset + size + _sizeofUint32 * index;
return reader.read(bc, offset);
},
growable: true,
);
}
}

/// Object that can read a value at a [BufferContext].
abstract class Reader<T> {
const Reader();
Expand Down Expand Up @@ -1121,6 +1157,25 @@ abstract class TableReader<T> extends Reader<T> {
}
}

/// A reader that wraps another reader if the type is a union.
///
/// This is useful for reading unions that can be stucts and need an extra
/// derefObject call.
class UnionReader<T> extends Reader<T?> {
final Reader<T>? reader;

const UnionReader(this.reader);

@override
int get size => 4;

@override
T? read(BufferContext bc, int offset) {
if (reader is StructReader) offset = bc.derefObject(offset);
return reader?.read(bc, offset);
}
}

/// Reader of lists of unsigned 32-bit integer values.
///
/// The returned unmodifiable lists lazily read values on access.
Expand Down Expand Up @@ -1311,6 +1366,29 @@ class _FbGenericList<E> extends _FbList<E> {
}
}

/// Lazy list of union objects
class _FbUnionList<E> extends _FbList<E?> {
final Reader<E>? Function(int index) getElementReader;

List<E?>? _items;

_FbUnionList(this.getElementReader, BufferContext bp, int offset)
: super(bp, offset);

@override
@pragma('vm:prefer-inline')
E? operator [](int i) {
_items ??= List<E?>.filled(length, null);
var item = _items![i];
final reader = getElementReader(i);
if (item == null && reader != null) {
item = reader.read(bc, offset + 4 + 4 * i);
_items![i] = item;
}
return item!;
}
}

/// The base class for immutable lists read from flat buffers.
abstract class _FbList<E> extends Object with ListMixin<E> implements List<E> {
final BufferContext bc;
Expand Down
77 changes: 77 additions & 0 deletions dart/test/flat_buffers_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import './bool_structs_generated.dart' as example4;
import './monster_test_my_game.example2_generated.dart' as example2;
import './monster_test_my_game.example_generated.dart' as example;
import 'enums_generated.dart' as example3;
import 'union_vector_generated.dart' as example5;

main() {
defineReflectiveSuite(() {
Expand All @@ -18,6 +19,7 @@ main() {
defineReflectiveTests(CheckOtherLangaugesData);
defineReflectiveTests(GeneratorTest);
defineReflectiveTests(ListOfEnumsTest);
defineReflectiveTests(UnionVectorTest);
});
}

Expand Down Expand Up @@ -934,6 +936,81 @@ class ObjectAPITest {
}
}

@reflectiveTest
class UnionVectorTest {
void test_unionVector() {
final movie = example5.MovieT(
mainCharacterType: example5.CharacterTypeId.Rapunzel,
mainCharacter: example5.RapunzelT(hairLength: 42),
charactersType: [
example5.CharacterTypeId.MuLan,
example5.CharacterTypeId.Rapunzel,
example5.CharacterTypeId.Belle,
example5.CharacterTypeId.BookFan,
example5.CharacterTypeId.Other,
example5.CharacterTypeId.Unused,
],
characters: [
example5.AttackerT(swordAttackDamage: 10),
example5.RapunzelT(hairLength: 203),
example5.BookReaderT(booksRead: 21),
example5.BookReaderT(booksRead: 500),
"Hello",
"World",
],
);

final fbb = Builder();
fbb.finish(movie.pack(fbb));

final movie2 = example5.Movie(fbb.buffer);
expect(
movie2.toString().replaceAllMapped(
RegExp('([a-zA-Z0-9]+){'), (match) => match.group(1)! + 'T{'),
movie.toString(),
);

final movie3 = movie2.unpack();
expect(movie3.toString(), movie.toString());

final movie4 = example5.MovieT(
mainCharacter: "String",
mainCharacterType: example5.CharacterTypeId.Other,
);

fbb.reset();
fbb.finish(movie4.pack(fbb));

final movie5 = example5.Movie(fbb.buffer);
expect(
movie5.toString().replaceAllMapped(
RegExp('([a-zA-Z0-9]+){'), (match) => match.group(1)! + 'T{'),
movie4.toString(),
);

final movie6 = movie5.unpack();
expect(movie6.toString(), movie4.toString());

final movie7 = example5.MovieT(
mainCharacter: example5.AttackerT(swordAttackDamage: 43),
mainCharacterType: example5.CharacterTypeId.MuLan,
);

fbb.reset();
fbb.finish(movie7.pack(fbb));

final movie8 = example5.Movie(fbb.buffer);
expect(
movie8.toString().replaceAllMapped(
RegExp('([a-zA-Z0-9]+){'), (match) => match.group(1)! + 'T{'),
movie7.toString(),
);

final movie9 = movie8.unpack();
expect(movie9.toString(), movie7.toString());
}
}

class StringListWrapperImpl {
final BufferContext bp;
final int offset;
Expand Down
26 changes: 16 additions & 10 deletions dart/test/keyword_test_keyword_test_generated.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,18 @@ class _KeywordsInUnionTypeIdReader extends fb.Reader<KeywordsInUnionTypeId> {
KeywordsInUnionTypeId.fromValue(const fb.Uint8Reader().read(bc, offset));
}

class _KeywordsInUnionReader extends fb.UnionReader {
_KeywordsInUnionReader(KeywordsInUnionTypeId? type) : super(_get(type));

static fb.Reader? _get(KeywordsInUnionTypeId? type) {
switch (type?.value) {
case 1: return KeywordsInTable.reader;
case 2: return KeywordsInTable.reader;
default: return null;
}
}
}

class KeywordsInTable {
KeywordsInTable._(this._bc, this._bcOffset);
factory KeywordsInTable(List<int> bytes) {
Expand Down Expand Up @@ -262,13 +274,7 @@ class Table2 {
final int _bcOffset;

KeywordsInUnionTypeId? get typeType => KeywordsInUnionTypeId._createOrNull(const fb.Uint8Reader().vTableGetNullable(_bc, _bcOffset, 4));
dynamic get type {
switch (typeType?.value) {
case 1: return KeywordsInTable.reader.vTableGetNullable(_bc, _bcOffset, 6);
case 2: return KeywordsInTable.reader.vTableGetNullable(_bc, _bcOffset, 6);
default: return null;
}
}
dynamic get type => _KeywordsInUnionReader(typeType).vTableGetNullable(_bc, _bcOffset, 6);

@override
String toString() {
Expand All @@ -277,7 +283,7 @@ class Table2 {

Table2T unpack() => Table2T(
typeType: typeType,
type: type?.unpack());
type: type is String ? type : type?.unpack());

static int pack(fb.Builder fbBuilder, Table2T? object) {
if (object == null) return 0;
Expand All @@ -295,7 +301,7 @@ class Table2T implements fb.Packable {

@override
int pack(fb.Builder fbBuilder) {
final int? typeOffset = type?.pack(fbBuilder);
final int? typeOffset = type is String ? fbBuilder.writeString(type) : type?.pack(fbBuilder);
fbBuilder.startTable(2);
fbBuilder.addUint8(0, typeType?.value);
fbBuilder.addOffset(1, typeOffset);
Expand Down Expand Up @@ -353,7 +359,7 @@ class Table2ObjectBuilder extends fb.ObjectBuilder {
/// Finish building, and store into the [fbBuilder].
@override
int finish(fb.Builder fbBuilder) {
final int? typeOffset = _type?.getOrCreateOffset(fbBuilder);
final int? typeOffset = _type is String ? fbBuilder.writeString(_type) : _type?.getOrCreateOffset(fbBuilder);
fbBuilder.startTable(2);
fbBuilder.addUint8(0, _typeType?.value);
fbBuilder.addOffset(1, typeOffset);
Expand Down
Loading