Skip to content

Commit ff3c9ac

Browse files
committed
flatbuffers - use eager list readers where necessary
1 parent 36b137f commit ff3c9ac

File tree

6 files changed

+142
-3
lines changed

6 files changed

+142
-3
lines changed

generator/lib/src/code_chunks.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,20 +132,24 @@ class CodeChunks {
132132
String fbReader;
133133
switch (p.type) {
134134
case OBXPropertyType.ByteVector:
135-
fbReader = 'fb.ListReader<int>(fb.Int8Reader())';
136135
if (['Int8List', 'Uint8List'].contains(p.dartFieldType)) {
136+
// No need for the eager reader here. We need to call fromList()
137+
// constructor anyway - there's no Int8List.generate() factory.
138+
fbReader = 'fb.ListReader<int>(fb.Int8Reader())';
137139
return '''{
138140
final list = ${fbReader}.vTableGet(buffer, rootOffset, ${propertyFlatBuffersvTableOffset(p)});
139141
object.${propertyFieldName(p)} = list == null ? null : ${p.dartFieldType}.fromList(list);
140142
}''';
143+
} else {
144+
fbReader = 'EagerListReader<int>(fb.Int8Reader())';
141145
}
142146
break;
143147
case OBXPropertyType.Relation:
144148
fbReader = 'fb.${_propertyFlatBuffersType[p.type]}Reader()';
145149
return 'object.${propertyFieldName(p)}.targetId = ${fbReader}.vTableGet(buffer, rootOffset, ${propertyFlatBuffersvTableOffset(p)});'
146150
'\n object.${propertyFieldName(p)}.attach(store);';
147151
case OBXPropertyType.StringVector:
148-
fbReader = 'fb.ListReader<String>(fb.StringReader())';
152+
fbReader = 'EagerListReader<String>(fb.StringReader())';
149153
break;
150154
default:
151155
fbReader = 'fb.${_propertyFlatBuffersType[p.type]}Reader()';

objectbox/lib/flatbuffers/flat_buffers.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ typedef void StructBuilder();
3939
class BufferContext {
4040
final ByteData _buffer;
4141

42+
ByteData get buffer => _buffer;
43+
4244
factory BufferContext.fromBytes(List<int> byteList) {
4345
Uint8List uint8List = _asUint8List(byteList);
4446
ByteData buf = new ByteData.view(uint8List.buffer, uint8List.offsetInBytes);

objectbox/lib/internal.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/// Don't import into your own code, use 'objectbox.dart' instead.
33
library objectbox_internal;
44

5+
export 'src/fb_readers.dart';
56
export 'src/model.dart';
67
export 'src/modelinfo/index.dart';
78
export 'src/query/query.dart'

objectbox/lib/src/fb_readers.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import 'dart:typed_data';
2+
3+
import '../flatbuffers/flat_buffers.dart';
4+
5+
/// Implements eager FlatBuffers list reader (default [ListReader] is lazy).
6+
class EagerListReader<E> extends Reader<List<E>> {
7+
final Reader<E> _elementReader;
8+
9+
/// Create a reader with the given element reader
10+
const EagerListReader(this._elementReader);
11+
12+
/// offset size (uint32), see [ListReader]
13+
@override
14+
int get size => 4;
15+
16+
@override
17+
List<E> read(BufferContext bc, int offset) {
18+
final listOffset = bc.derefObject(offset);
19+
final length = bc.buffer.getUint32(listOffset, Endian.little);
20+
return List<E>.generate(
21+
length,
22+
(int index) => _elementReader.read(
23+
bc, listOffset + size + _elementReader.size * index),
24+
growable: true);
25+
}
26+
}

objectbox/test/entity.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,24 @@ class TestEntity {
7979
this.tUint8List,
8080
this.ignore});
8181

82+
TestEntity.filled({
83+
this.id,
84+
this.tString = 'Foo',
85+
this.tBool = true,
86+
this.tByte = 42,
87+
this.tChar = 24,
88+
this.tShort = 1234,
89+
this.tInt = 123456789,
90+
this.tLong = 123456789123456789,
91+
this.tFloat = 4.5,
92+
this.tDouble = 2.3,
93+
}) {
94+
tStrings = ['foo', 'bar'];
95+
tByteList = [1, 2, 3];
96+
tInt8List = Int8List.fromList([-4, 5, 6]);
97+
tUint8List = Uint8List.fromList([7, 8, 9]);
98+
}
99+
82100
TestEntity.ignoredExcept(this.tInt) {
83101
omit = -1;
84102
disregard = 1;

objectbox/test/flatbuffers_test.dart

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import 'dart:ffi';
22
import 'dart:typed_data';
33

4-
import 'package:test/test.dart';
54
import 'package:flat_buffers/flat_buffers.dart' as fb_upstream;
5+
import 'package:objectbox/internal.dart';
66
import 'package:objectbox/src/bindings/flatbuffers.dart';
7+
import 'package:test/test.dart';
8+
9+
import 'entity.dart';
10+
import 'objectbox.g.dart';
11+
import 'test_env.dart';
712

813
Uint8List addFbData(dynamic fbb) {
914
fbb.startTable();
@@ -38,4 +43,87 @@ void main() {
3843
fb1.clear();
3944
});
4045
});
46+
47+
final bytesSum =
48+
(ByteData data) => data.buffer.asInt8List().reduce((v, e) => v + e);
49+
50+
test('allocator', () {
51+
final allocator = Allocator();
52+
53+
final buf1 = allocator.allocate(1024);
54+
allocator.clear(buf1, true);
55+
56+
final buf2 = allocator.allocate(1024);
57+
allocator.clear(buf2, true);
58+
59+
expect(bytesSum(buf1), isZero);
60+
expect(bytesSum(buf2), isZero);
61+
62+
buf2.setInt8(42, 1);
63+
expect(bytesSum(buf1), isZero);
64+
expect(bytesSum(buf2), 1);
65+
66+
allocator.clear(buf2, true);
67+
expect(bytesSum(buf2), isZero);
68+
69+
allocator.deallocate(buf1);
70+
allocator.freeAll();
71+
});
72+
73+
// Note: only checks content initialized by TestEntity.filled
74+
void checkSameEntities(TestEntity a, TestEntity b) {
75+
expect(a.tString, b.tString);
76+
expect(a.tBool, b.tBool);
77+
expect(a.tByte, b.tByte);
78+
expect(a.tChar, b.tChar);
79+
expect(a.tShort, b.tShort);
80+
expect(a.tInt, b.tInt);
81+
expect(a.tLong, b.tLong);
82+
expect(a.tFloat, b.tFloat);
83+
expect(a.tDouble, b.tDouble);
84+
expect(a.tStrings, b.tStrings);
85+
expect(a.tByteList, b.tByteList);
86+
expect(a.tInt8List, b.tInt8List);
87+
expect(a.tUint8List, b.tUint8List);
88+
}
89+
90+
test('generated code', () {
91+
final env = TestEnv('fb');
92+
93+
final binding = getObjectBoxModel().bindings[TestEntity]
94+
as EntityDefinition<TestEntity>;
95+
96+
final source = TestEntity.filled();
97+
98+
final fb1 = BuilderWithCBuffer();
99+
binding.objectToFB(source, fb1.fbb);
100+
final fbData = fb1.bufPtr.cast<Uint8>().asTypedList(fb1.fbb.size);
101+
102+
// must have the same content after reading back
103+
final target = binding.objectFromFB(env.store, fbData);
104+
105+
// Note: we don't do check yet, because the default flatbuffers reader
106+
// reads lists lazily, on the first access and this would cause the next
107+
// [checkSameEntities()] after clearing the buffer to also pass.
108+
// checkSameEntities(target, source);
109+
110+
// explicitly clear the allocated memory
111+
fbMemset(fb1.bufPtr.cast<Uint8>(), 0, fbData.lengthInBytes);
112+
// fbData is now cleared as well, it's not a copy
113+
expect(bytesSum(fbData.buffer.asByteData()), isZero);
114+
115+
// clearing the data must not affect already read objects
116+
// Note: it previously did because of upstream flatbuffers lazy reading
117+
checkSameEntities(target, source);
118+
119+
// must be empty after reading again
120+
checkSameEntities(binding.objectFromFB(env.store, fbData), TestEntity());
121+
122+
// note: accessing fbData after fb1.clear() is illegal (memory is freed)
123+
fb1.clear();
124+
env.close();
125+
126+
// clearing the data must not affect already read objects
127+
checkSameEntities(target, source);
128+
});
41129
}

0 commit comments

Comments
 (0)