diff --git a/.agents/docs-and-formatting.md b/.agents/docs-and-formatting.md index acfa09fede..7d29e77b8f 100644 --- a/.agents/docs-and-formatting.md +++ b/.agents/docs-and-formatting.md @@ -13,6 +13,8 @@ Load this file when changing documentation, public APIs, protocol specs, benchma ## Rules +- Do not format Markdown under `tasks/`, including task design, plan, progress, state, history, + and lessons files. These files are agent working state rather than repository documentation. - Update the relevant docs under `docs/` when important public APIs change. - Update `docs/specification/**` when protocol behavior changes. - Keep examples working and aligned with the current API and protocol behavior. @@ -32,8 +34,6 @@ Load this file when changing documentation, public APIs, protocol specs, benchma ## Formatting Commands - Markdown: `prettier --write ` -- Do not format Markdown under `tasks/`, including task design, plan, progress, state, history, - and lessons files. These files are agent working state rather than repository documentation. - Python code, including `compiler/`, `benchmarks/`, `integration_tests/`, and `python/`: `python -m ruff format ` and `python -m ruff check --fix ` diff --git a/README.md b/README.md index fdef527b63..7d019aa1f4 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ For more detailed benchmarks and methodology, see [Go Benchmark](benchmarks/go).

-For more detailed benchmarks and methodology, see [Python](benchmarks/python). +For more detailed benchmarks and methodology, see [Python Benchmarks](benchmarks/python). ### JavaScript/NodeJS Serialization Performance diff --git a/dart/packages/fory-test/lib/entity/xlang_test_manual.dart b/dart/packages/fory-test/lib/entity/xlang_test_manual.dart index 821045e973..f1f1f2e5ec 100644 --- a/dart/packages/fory-test/lib/entity/xlang_test_manual.dart +++ b/dart/packages/fory-test/lib/entity/xlang_test_manual.dart @@ -22,7 +22,6 @@ library; // ignore_for_file: implementation_imports, invalid_use_of_internal_member import 'package:fory/fory.dart'; -import 'package:fory/src/serializer/serializer_support.dart'; import 'xlang_test_models.dart'; @@ -166,43 +165,17 @@ const List _refOverrideContainerForyFieldInfo = final GeneratedStructRegistration _refOverrideContainerForyRegistration = GeneratedStructRegistration( - fieldWritersBySlot: >[ - _writeRefOverrideContainerField0, - _writeRefOverrideContainerField1, - _writeRefOverrideContainerField2, - ], type: RefOverrideContainer, serializerFactory: _RefOverrideContainerForySerializer.new, evolving: true, + needsRootRef: true, + usesNestedTypeDefinitions: true, fields: _refOverrideContainerForyFieldInfo, ); -void _writeRefOverrideContainerField0( - WriteContext context, - GeneratedStructFieldInfo field, - RefOverrideContainer value, -) { - writeGeneratedStructFieldInfoValue(context, field, value.listField); -} - -void _writeRefOverrideContainerField1( - WriteContext context, - GeneratedStructFieldInfo field, - RefOverrideContainer value, -) { - writeGeneratedStructFieldInfoValue(context, field, value.setField); -} - -void _writeRefOverrideContainerField2( - WriteContext context, - GeneratedStructFieldInfo field, - RefOverrideContainer value, -) { - writeGeneratedStructFieldInfoValue(context, field, value.mapField); -} - final class _RefOverrideContainerForySerializer - extends Serializer { + extends Serializer + implements GeneratedStructSerializer { List? _generatedFields; _RefOverrideContainerForySerializer(); @@ -223,67 +196,67 @@ final class _RefOverrideContainerForySerializer @override void write(WriteContext context, RefOverrideContainer value) { - final slots = generatedStructWriteSlots(context); - if (slots == null) { - final fields = _writeFields(context); - writeGeneratedStructFieldInfoValue(context, fields[0], value.listField); - writeGeneratedStructFieldInfoValue(context, fields[1], value.setField); - writeGeneratedStructFieldInfoValue(context, fields[2], value.mapField); - return; - } - final writers = _refOverrideContainerForyRegistration.fieldWritersBySlot; - for (final field in slots.orderedFields) { - writers[field.slot](context, field, value); - } + final fields = _writeFields(context); + writeGeneratedStructFieldInfoValue(context, fields[0], value.listField); + writeGeneratedStructFieldInfoValue(context, fields[1], value.setField); + writeGeneratedStructFieldInfoValue(context, fields[2], value.mapField); } @override RefOverrideContainer read(ReadContext context) { - final slots = generatedStructReadSlots(context); final value = RefOverrideContainer(); - context.reference(value); - if (slots == null) { - final fields = _readFields(context); - value.listField = _readRefOverrideContainerListField( - readGeneratedStructFieldInfoValue(context, fields[0], value.listField), - value.listField, - ); - value.setField = _readRefOverrideContainerSetField( - readGeneratedStructFieldInfoValue(context, fields[1], value.setField), - value.setField, - ); - value.mapField = _readRefOverrideContainerMapField( - readGeneratedStructFieldInfoValue(context, fields[2], value.mapField), - value.mapField, - ); - return value; - } - if (slots.containsSlot(0)) { - final rawRefOverrideContainer0 = slots.valueForSlot(0); - value.listField = _readRefOverrideContainerListField( - rawRefOverrideContainer0 is DeferredReadRef - ? context.getReadRef(rawRefOverrideContainer0.id) - : rawRefOverrideContainer0, - value.listField, - ); - } - if (slots.containsSlot(1)) { - final rawRefOverrideContainer1 = slots.valueForSlot(1); - value.setField = _readRefOverrideContainerSetField( - rawRefOverrideContainer1 is DeferredReadRef - ? context.getReadRef(rawRefOverrideContainer1.id) - : rawRefOverrideContainer1, - value.setField, - ); - } - if (slots.containsSlot(2)) { - final rawRefOverrideContainer2 = slots.valueForSlot(2); - value.mapField = _readRefOverrideContainerMapField( - rawRefOverrideContainer2 is DeferredReadRef - ? context.getReadRef(rawRefOverrideContainer2.id) - : rawRefOverrideContainer2, - value.mapField, - ); + final fields = _readFields(context); + value.listField = _readRefOverrideContainerListField( + readGeneratedStructFieldInfoValue(context, fields[0], value.listField), + value.listField, + ); + value.setField = _readRefOverrideContainerSetField( + readGeneratedStructFieldInfoValue(context, fields[1], value.setField), + value.setField, + ); + value.mapField = _readRefOverrideContainerMapField( + readGeneratedStructFieldInfoValue(context, fields[2], value.mapField), + value.mapField, + ); + return value; + } + + @override + RefOverrideContainer readCompatibleStruct( + ReadContext context, + CompatibleStructReadLayout layout, + ) { + final value = RefOverrideContainer(); + for (var index = 0; index < layout.fieldCount; index += 1) { + final field = layout.localFieldAt(index); + if (field == null) { + skipGeneratedCompatibleStructField(context, layout, index); + continue; + } + switch (field.index) { + case 0: + value.listField = _readRefOverrideContainerListField( + readGeneratedCompatibleStructField(context, layout, index), + value.listField, + ); + break; + case 1: + value.setField = _readRefOverrideContainerSetField( + readGeneratedCompatibleStructField(context, layout, index), + value.setField, + ); + break; + case 2: + value.mapField = _readRefOverrideContainerMapField( + readGeneratedCompatibleStructField(context, layout, index), + value.mapField, + ); + break; + default: + throw StateError( + 'Compatible field index is out of range for RefOverrideContainer.', + ); + } } return value; } diff --git a/dart/packages/fory-test/test/compatible_struct_slots_test.dart b/dart/packages/fory-test/test/compatible_struct_read_test.dart similarity index 96% rename from dart/packages/fory-test/test/compatible_struct_slots_test.dart rename to dart/packages/fory-test/test/compatible_struct_read_test.dart index bf2f3a216a..478bf21540 100644 --- a/dart/packages/fory-test/test/compatible_struct_slots_test.dart +++ b/dart/packages/fory-test/test/compatible_struct_read_test.dart @@ -22,7 +22,7 @@ import 'package:fory_test/entity/xlang_test_models.dart'; import 'package:test/test.dart'; void main() { - test('compatible named struct round trip scopes nested struct slots', () { + test('compatible named struct round trip preserves nested struct fields', () { final fory = Fory(compatible: true); registerXlangType( fory, diff --git a/dart/packages/fory/lib/src/codegen/fory_generator.dart b/dart/packages/fory/lib/src/codegen/fory_generator.dart index 02483ea7d7..8ca52f903e 100644 --- a/dart/packages/fory/lib/src/codegen/fory_generator.dart +++ b/dart/packages/fory/lib/src/codegen/fory_generator.dart @@ -460,17 +460,23 @@ final class ForyGenerator extends Generator { final hasRuntimeFastPath = structSpec.fields.any( (field) => !_usesDirectGeneratedBasicFastPath(field), ); - final directCursorRuns = _directGeneratedWriteReservationRuns( + final writeUsesBuffer = structSpec.fields.any( + _directGeneratedBasicWriteNeedsBuffer, + ); + final readUsesBuffer = structSpec.fields.any( + _directGeneratedBasicReadNeedsBuffer, + ); + final directPrimitiveRuns = _directGeneratedPrimitiveRuns( structSpec.fields, ); - final directCursorRunByStart = { - for (final run in directCursorRuns) run.start: run, + final directPrimitiveRunByStart = { + for (final run in directPrimitiveRuns) run.start: run, }; - final directCursorRunByEnd = { - for (final run in directCursorRuns) run.end: run, + final directPrimitiveRunByEnd = { + for (final run in directPrimitiveRuns) run.end: run, }; - final directCursorStartByIndex = { - for (final run in directCursorRuns) + final directPrimitiveRunStartByIndex = { + for (final run in directPrimitiveRuns) for (var index = run.start; index <= run.end; index += 1) index: run.start, }; @@ -484,79 +490,24 @@ final class ForyGenerator extends Generator { output ..writeln('];') ..writeln() - ..writeln( - 'typedef _${structSpec.name}FieldWriter = GeneratedStructFieldInfoWriter<${structSpec.name}>;', - ); - if (structSpec.constructorPlan.mode == _ConstructorMode.mutable) { - output.writeln( - 'typedef _${structSpec.name}FieldReader = GeneratedStructFieldInfoReader<${structSpec.name}>;', - ); - } - output.writeln(); - for (var index = 0; index < structSpec.fields.length; index += 1) { - final field = structSpec.fields[index]; - final fieldValue = - _generatedFieldInfoWriteValueExpression(field, 'value.${field.name}'); - output - ..writeln( - 'void _write${structSpec.name}Field$index(WriteContext context, GeneratedStructFieldInfo field, ${structSpec.name} value) {', - ) - ..writeln( - ' writeGeneratedStructFieldInfoValue(context, field, $fieldValue);', - ) - ..writeln('}') - ..writeln(); - } - if (structSpec.constructorPlan.mode == _ConstructorMode.mutable) { - for (var index = 0; index < structSpec.fields.length; index += 1) { - final field = structSpec.fields[index]; - final readerFunctionName = field.readerFunctionName(structSpec.name); - output - ..writeln( - 'void _read${structSpec.name}Field$index(ReadContext context, ${structSpec.name} value, Object? rawValue) {', - ) - ..writeln( - ' value.${field.name} = $readerFunctionName(${_slotResolvedRawExpression('rawValue')}, value.${field.name});', - ) - ..writeln('}') - ..writeln(); - } - } - output ..writeln( 'final GeneratedStructRegistration<${structSpec.name}> $registrationName = GeneratedStructRegistration<${structSpec.name}>(', - ) - ..writeln(' fieldWritersBySlot: <_${structSpec.name}FieldWriter>['); - for (var index = 0; index < structSpec.fields.length; index += 1) { - output.writeln(' _write${structSpec.name}Field$index,'); - } - output - ..writeln(' ],') - ..writeln( - structSpec.constructorPlan.mode == _ConstructorMode.mutable - ? ' compatibleFactory: ${structSpec.name}.new,' - : ' compatibleFactory: null,', - ); - if (structSpec.constructorPlan.mode == _ConstructorMode.mutable) { - output.writeln( - ' compatibleReadersBySlot: <_${structSpec.name}FieldReader>[', ); - for (var index = 0; index < structSpec.fields.length; index += 1) { - output.writeln(' _read${structSpec.name}Field$index,'); - } - output.writeln(' ],'); - } else { - output.writeln(' compatibleReadersBySlot: null,'); - } output ..writeln(' type: ${structSpec.name},') ..writeln(' serializerFactory: _${structSpec.name}ForySerializer.new,') ..writeln(' evolving: ${structSpec.evolving},') + ..writeln( + ' needsRootRef: ${_structNeedsEarlyReadReference(structSpec)},', + ) + ..writeln( + ' usesNestedTypeDefinitions: ${_structUsesNestedTypeDefinitions(structSpec)},', + ) ..writeln(' fields: $metadataListName,') ..writeln(');') ..writeln() ..writeln( - 'final class $serializerClassName extends Serializer<${structSpec.name}> {', + 'final class $serializerClassName extends Serializer<${structSpec.name}> implements GeneratedStructSerializer<${structSpec.name}> {', ) ..writeln(' List? _generatedFields;') ..writeln() @@ -586,10 +537,8 @@ final class ForyGenerator extends Generator { ..writeln(' @override') ..writeln( ' void write(WriteContext context, ${structSpec.name} value) {', - ) - ..writeln(' final slots = generatedStructWriteSlots(context);') - ..writeln(' if (slots == null) {'); - if (directCursorRuns.isNotEmpty) { + ); + if (writeUsesBuffer) { output.writeln(' final buffer = context.buffer;'); } if (hasRuntimeFastPath) { @@ -597,15 +546,23 @@ final class ForyGenerator extends Generator { } for (var index = 0; index < structSpec.fields.length; index += 1) { final field = structSpec.fields[index]; - final directCursorRun = directCursorRunByStart[index]; - if (directCursorRun != null) { - output.writeln( - ' final cursor$index = GeneratedWriteCursor.reserve(buffer, ${directCursorRun.bytes});', + final directPrimitiveRun = directPrimitiveRunByStart[index]; + if (directPrimitiveRun != null) { + _writeDirectGeneratedWriteRunStart( + output, + structSpec.fields, + directPrimitiveRun, + ' ', ); } if (_usesReservedGeneratedFastPath(field)) { - output.writeln( - ' ${_directGeneratedCursorWriteStatement(field, 'cursor${directCursorStartByIndex[index]}', 'value.${field.name}')};', + _writeDirectGeneratedBufferWriteStatement( + output, + field, + directPrimitiveRunStartByIndex[index]!, + index, + 'value.${field.name}', + ' ', ); } else if (_usesDirectGeneratedBasicFastPath(field)) { output.writeln( @@ -624,18 +581,16 @@ final class ForyGenerator extends Generator { ' writeGeneratedStructFieldInfoValue(context, fields[$index], $fieldValue);', ); } - final directCursorEndRun = directCursorRunByEnd[index]; - if (directCursorEndRun != null) { - output.writeln(' cursor${directCursorEndRun.start}.finish();'); + final directPrimitiveEndRun = directPrimitiveRunByEnd[index]; + if (directPrimitiveEndRun != null) { + _writeDirectGeneratedWriteRunEnd( + output, + directPrimitiveEndRun, + ' ', + ); } } output - ..writeln(' return;') - ..writeln(' }') - ..writeln(' final writers = $registrationName.fieldWritersBySlot;') - ..writeln(' for (final field in slots.orderedFields) {') - ..writeln(' writers[field.slot](context, field, value);') - ..writeln(' }') ..writeln(' }') ..writeln() ..writeln(' @override') @@ -643,12 +598,14 @@ final class ForyGenerator extends Generator { switch (structSpec.constructorPlan.mode) { case _ConstructorMode.mutable: - output - ..writeln(' final slots = generatedStructReadSlots(context);') - ..writeln(' final value = ${structSpec.name}();') - ..writeln(' context.reference(value);') - ..writeln(' if (slots == null) {'); - if (directCursorRuns.isNotEmpty) { + output.writeln(' final value = ${structSpec.name}();'); + if (_structNeedsEarlyReadReference(structSpec)) { + output + ..writeln(' if (context.hasPreservedRefId) {') + ..writeln(' context.reference(value);') + ..writeln(' }'); + } + if (readUsesBuffer) { output.writeln(' final buffer = context.buffer;'); } if (hasRuntimeFastPath) { @@ -657,15 +614,23 @@ final class ForyGenerator extends Generator { for (var index = 0; index < structSpec.fields.length; index += 1) { final field = structSpec.fields[index]; final readerFunctionName = field.readerFunctionName(structSpec.name); - final directCursorRun = directCursorRunByStart[index]; - if (directCursorRun != null) { - output.writeln( - ' final cursor$index = GeneratedReadCursor.start(buffer);', + final directPrimitiveRun = directPrimitiveRunByStart[index]; + if (directPrimitiveRun != null) { + _writeDirectGeneratedReadRunStart( + output, + structSpec.fields, + directPrimitiveRun, + ' ', ); } if (_usesReservedGeneratedFastPath(field)) { - output.writeln( - ' value.${field.name} = ${_directGeneratedCursorReadExpression(field, 'cursor${directCursorStartByIndex[index]}')};', + _writeDirectGeneratedBufferReadStatement( + output, + field, + directPrimitiveRunStartByIndex[index]!, + index, + 'value.${field.name}', + ' ', ); } else if (_usesDirectGeneratedBasicFastPath(field)) { output.writeln( @@ -688,31 +653,18 @@ final class ForyGenerator extends Generator { ' value.${field.name} = $readerFunctionName(readGeneratedStructFieldInfoValue(context, fields[$index], value.${field.name}), value.${field.name});', ); } - final directCursorEndRun = directCursorRunByEnd[index]; - if (directCursorEndRun != null) { - output.writeln(' cursor${directCursorEndRun.start}.finish();'); + final directPrimitiveEndRun = directPrimitiveRunByEnd[index]; + if (directPrimitiveEndRun != null) { + _writeDirectGeneratedReadRunEnd( + output, + directPrimitiveEndRun, + ' ', + ); } } - output.writeln(' return value;'); - output.writeln(' }'); - for (var index = 0; index < structSpec.fields.length; index += 1) { - final field = structSpec.fields[index]; - final readerFunctionName = field.readerFunctionName(structSpec.name); - final rawValueName = 'raw${structSpec.name}$index'; - output.writeln(' if (slots.containsSlot($index)) {'); - output.writeln( - ' final $rawValueName = slots.valueForSlot($index);', - ); - output.writeln( - ' value.${field.name} = $readerFunctionName(${_slotResolvedRawExpression(rawValueName)}, value.${field.name});', - ); - output.writeln(' }'); - } output.writeln(' return value;'); case _ConstructorMode.constructor: - output.writeln(' final slots = generatedStructReadSlots(context);'); - output.writeln(' if (slots == null) {'); - if (directCursorRuns.isNotEmpty) { + if (readUsesBuffer) { output.writeln(' final buffer = context.buffer;'); } if (hasRuntimeFastPath) { @@ -721,15 +673,23 @@ final class ForyGenerator extends Generator { for (var index = 0; index < structSpec.fields.length; index += 1) { final field = structSpec.fields[index]; final readerFunctionName = field.readerFunctionName(structSpec.name); - final directCursorRun = directCursorRunByStart[index]; - if (directCursorRun != null) { - output.writeln( - ' final cursor$index = GeneratedReadCursor.start(buffer);', + final directPrimitiveRun = directPrimitiveRunByStart[index]; + if (directPrimitiveRun != null) { + _writeDirectGeneratedReadRunStart( + output, + structSpec.fields, + directPrimitiveRun, + ' ', ); } if (_usesReservedGeneratedFastPath(field)) { - output.writeln( - ' final ${field.displayType} ${field.localName} = ${_directGeneratedCursorReadExpression(field, 'cursor${directCursorStartByIndex[index]}')};', + _writeDirectGeneratedBufferReadStatement( + output, + field, + directPrimitiveRunStartByIndex[index]!, + index, + 'final ${field.displayType} ${field.localName}', + ' ', ); } else if (_usesDirectGeneratedBasicFastPath(field)) { output.writeln( @@ -752,15 +712,17 @@ final class ForyGenerator extends Generator { ' final ${field.displayType} ${field.localName} = $readerFunctionName(readGeneratedStructFieldInfoValue(context, fields[$index]));', ); } - final directCursorEndRun = directCursorRunByEnd[index]; - if (directCursorEndRun != null) { - output.writeln(' cursor${directCursorEndRun.start}.finish();'); + final directPrimitiveEndRun = directPrimitiveRunByEnd[index]; + if (directPrimitiveEndRun != null) { + _writeDirectGeneratedReadRunEnd( + output, + directPrimitiveEndRun, + ' ', + ); } } final constructorInvocation = _constructorInvocation(structSpec); - output - ..writeln(' final value = $constructorInvocation;') - ..writeln(' context.reference(value);'); + output.writeln(' final value = $constructorInvocation;'); for (final fieldName in structSpec.constructorPlan.postConstructionFieldNames) { final field = structSpec.fields.firstWhere( @@ -768,48 +730,12 @@ final class ForyGenerator extends Generator { ); output.writeln(' value.${field.name} = ${field.localName};'); } - output.writeln(' return value;'); - // Slow path: schema-evolution slots present. Use `late final` so each - // field can be conditionally assigned from its slot or read fresh. - output.writeln(' }'); - for (var index = 0; index < structSpec.fields.length; index += 1) { - final field = structSpec.fields[index]; - output.writeln( - ' late final ${field.displayType} ${field.localName};', - ); - } - for (var index = 0; index < structSpec.fields.length; index += 1) { - final field = structSpec.fields[index]; - final readerFunctionName = field.readerFunctionName(structSpec.name); - final rawValueName = 'raw${structSpec.name}$index'; - output.writeln(' if (slots.containsSlot($index)) {'); - output.writeln( - ' final $rawValueName = slots.valueForSlot($index);', - ); - output.writeln( - ' ${field.localName} = $readerFunctionName(${_slotResolvedRawExpression(rawValueName)});', - ); - output.writeln(' } else {'); - output.writeln( - ' ${field.localName} = $readerFunctionName(null);', - ); - output.writeln(' }'); - } - output - ..writeln(' final value = $constructorInvocation;') - ..writeln(' context.reference(value);'); - for (final fieldName - in structSpec.constructorPlan.postConstructionFieldNames) { - final field = structSpec.fields.firstWhere( - (item) => item.name == fieldName, - ); - output.writeln(' value.${field.name} = ${field.localName};'); - } output.writeln(' return value;'); } + output.writeln(' }'); + _writeCompatibleStructReadMethod(output, structSpec); output - ..writeln(' }') ..writeln('}') ..writeln(); @@ -830,6 +756,114 @@ final class ForyGenerator extends Generator { } } + void _writeCompatibleStructReadMethod( + StringBuffer output, + _GeneratedStructSpec structSpec, + ) { + output + ..writeln() + ..writeln(' @override') + ..writeln( + ' ${structSpec.name} readCompatibleStruct(ReadContext context, CompatibleStructReadLayout layout) {', + ); + switch (structSpec.constructorPlan.mode) { + case _ConstructorMode.mutable: + output.writeln(' final value = ${structSpec.name}();'); + if (_structNeedsEarlyReadReference(structSpec)) { + output + ..writeln(' if (context.hasPreservedRefId) {') + ..writeln(' context.reference(value);') + ..writeln(' }'); + } + output + ..writeln( + ' for (var index = 0; index < layout.fieldCount; index += 1) {', + ) + ..writeln(' final field = layout.localFieldAt(index);') + ..writeln(' if (field == null) {') + ..writeln( + ' skipGeneratedCompatibleStructField(context, layout, index);', + ) + ..writeln(' continue;') + ..writeln(' }') + ..writeln(' switch (field.index) {'); + for (var index = 0; index < structSpec.fields.length; index += 1) { + final field = structSpec.fields[index]; + final readerFunctionName = field.readerFunctionName(structSpec.name); + output + ..writeln(' case $index:') + ..writeln( + ' value.${field.name} = $readerFunctionName(readGeneratedCompatibleStructField(context, layout, index), value.${field.name});', + ) + ..writeln(' break;'); + } + output + ..writeln(' default:') + ..writeln( + " throw StateError('Compatible field index is out of range for ${structSpec.name}.');", + ) + ..writeln(' }') + ..writeln(' }') + ..writeln(' return value;'); + case _ConstructorMode.constructor: + for (var index = 0; index < structSpec.fields.length; index += 1) { + final field = structSpec.fields[index]; + output + ..writeln(' late final ${field.displayType} ${field.localName};') + ..writeln(' var hasField$index = false;'); + } + output + ..writeln( + ' for (var index = 0; index < layout.fieldCount; index += 1) {', + ) + ..writeln(' final field = layout.localFieldAt(index);') + ..writeln(' if (field == null) {') + ..writeln( + ' skipGeneratedCompatibleStructField(context, layout, index);', + ) + ..writeln(' continue;') + ..writeln(' }') + ..writeln(' switch (field.index) {'); + for (var index = 0; index < structSpec.fields.length; index += 1) { + final field = structSpec.fields[index]; + final readerFunctionName = field.readerFunctionName(structSpec.name); + output + ..writeln(' case $index:') + ..writeln( + ' ${field.localName} = $readerFunctionName(readGeneratedCompatibleStructField(context, layout, index));', + ) + ..writeln(' hasField$index = true;') + ..writeln(' break;'); + } + output + ..writeln(' default:') + ..writeln( + " throw StateError('Compatible field index is out of range for ${structSpec.name}.');", + ) + ..writeln(' }') + ..writeln(' }'); + for (var index = 0; index < structSpec.fields.length; index += 1) { + final field = structSpec.fields[index]; + final readerFunctionName = field.readerFunctionName(structSpec.name); + output + ..writeln(' if (!hasField$index) {') + ..writeln(' ${field.localName} = $readerFunctionName(null);') + ..writeln(' }'); + } + final constructorInvocation = _constructorInvocation(structSpec); + output.writeln(' final value = $constructorInvocation;'); + for (final fieldName + in structSpec.constructorPlan.postConstructionFieldNames) { + final field = structSpec.fields.firstWhere( + (item) => item.name == fieldName, + ); + output.writeln(' value.${field.name} = ${field.localName};'); + } + output.writeln(' return value;'); + } + output.writeln(' }'); + } + void _writeRegistrationHelpers( StringBuffer output, { required List<_GeneratedEnumSpec> enumSpecs, @@ -934,10 +968,6 @@ final class ForyGenerator extends Generator { return '${structSpec.name}($arguments)'; } - String _slotResolvedRawExpression(String rawValueExpression) { - return 'resolveGeneratedSlotRawValue(context, $rawValueExpression)'; - } - bool _isSkipped(FieldElement field) { final annotation = _fieldAnnotationOf(field); if (annotation == null) { @@ -1184,6 +1214,66 @@ GeneratedFieldType( field.fieldType.typeId == TypeIds.enumById; } + bool _directGeneratedBasicWriteNeedsBuffer(_GeneratedFieldSpec field) { + if (!_usesDirectGeneratedBasicFastPath(field)) { + return false; + } + switch (field.fieldType.typeId) { + case TypeIds.string: + case TypeIds.binary: + case TypeIds.decimal: + case TypeIds.date: + case TypeIds.duration: + case TypeIds.timestamp: + case TypeIds.boolArray: + case TypeIds.int8Array: + case TypeIds.int16Array: + case TypeIds.int32Array: + case TypeIds.int64Array: + case TypeIds.uint8Array: + case TypeIds.uint16Array: + case TypeIds.uint32Array: + case TypeIds.uint64Array: + case TypeIds.float16Array: + case TypeIds.bfloat16Array: + case TypeIds.float32Array: + case TypeIds.float64Array: + return false; + default: + return true; + } + } + + bool _directGeneratedBasicReadNeedsBuffer(_GeneratedFieldSpec field) { + if (!_usesDirectGeneratedBasicFastPath(field)) { + return false; + } + switch (field.fieldType.typeId) { + case TypeIds.string: + case TypeIds.binary: + case TypeIds.decimal: + case TypeIds.date: + case TypeIds.duration: + case TypeIds.timestamp: + case TypeIds.boolArray: + case TypeIds.int8Array: + case TypeIds.int16Array: + case TypeIds.int32Array: + case TypeIds.int64Array: + case TypeIds.uint8Array: + case TypeIds.uint16Array: + case TypeIds.uint32Array: + case TypeIds.uint64Array: + case TypeIds.float16Array: + case TypeIds.bfloat16Array: + case TypeIds.float32Array: + case TypeIds.float64Array: + return false; + default: + return true; + } + } + bool _usesDirectGeneratedDeclaredReadFastPath(_GeneratedFieldSpec field) { if (field.fieldType.nullable || field.fieldType.ref || @@ -1264,17 +1354,20 @@ GeneratedFieldType( ); } - List<_DirectGeneratedWriteReservationRun> - _directGeneratedWriteReservationRuns(List<_GeneratedFieldSpec> fields) { - final runs = <_DirectGeneratedWriteReservationRun>[]; + List<_DirectGeneratedPrimitiveRun> _directGeneratedPrimitiveRuns( + List<_GeneratedFieldSpec> fields, + ) { + final runs = <_DirectGeneratedPrimitiveRun>[]; int? start; var bytes = 0; for (var index = 0; index < fields.length; index += 1) { - final fieldBytes = _directGeneratedWriteReservationBytes(fields[index]); + final fieldBytes = _directGeneratedPrimitiveReservationBytes( + fields[index], + ); if (fieldBytes == null) { if (start != null) { runs.add( - _DirectGeneratedWriteReservationRun(start, index - 1, bytes), + _DirectGeneratedPrimitiveRun(start, index - 1, bytes), ); start = null; bytes = 0; @@ -1286,17 +1379,17 @@ GeneratedFieldType( } if (start != null) { runs.add( - _DirectGeneratedWriteReservationRun(start, fields.length - 1, bytes), + _DirectGeneratedPrimitiveRun(start, fields.length - 1, bytes), ); } return runs; } bool _usesReservedGeneratedFastPath(_GeneratedFieldSpec field) { - return _directGeneratedWriteReservationBytes(field) != null; + return _directGeneratedPrimitiveReservationBytes(field) != null; } - int? _directGeneratedWriteReservationBytes(_GeneratedFieldSpec field) { + int? _directGeneratedPrimitiveReservationBytes(_GeneratedFieldSpec field) { if (!_usesDirectGeneratedBasicFastPath(field)) { return null; } @@ -1314,30 +1407,327 @@ GeneratedFieldType( case TypeIds.uint32: case TypeIds.float32: return 4; - case TypeIds.date: - return 10; - case TypeIds.int64: - case TypeIds.uint64: case TypeIds.float64: return 8; - case TypeIds.duration: - return 14; - case TypeIds.timestamp: - return 12; case TypeIds.varInt32: case TypeIds.varUint32: - case TypeIds.enumById: return 5; - case TypeIds.varInt64: - case TypeIds.taggedInt64: - case TypeIds.varUint64: - case TypeIds.taggedUint64: - return 10; default: return null; } } + bool _directGeneratedRunUsesBytes( + List<_GeneratedFieldSpec> fields, + _DirectGeneratedPrimitiveRun run, + ) { + for (var index = run.start; index <= run.end; index += 1) { + switch (fields[index].fieldType.typeId) { + case TypeIds.boolType: + case TypeIds.varInt32: + case TypeIds.varUint32: + return true; + } + } + return false; + } + + bool _directGeneratedRunUsesView( + List<_GeneratedFieldSpec> fields, + _DirectGeneratedPrimitiveRun run, + ) { + for (var index = run.start; index <= run.end; index += 1) { + switch (fields[index].fieldType.typeId) { + case TypeIds.boolType: + case TypeIds.varInt32: + case TypeIds.varUint32: + break; + default: + return true; + } + } + return false; + } + + void _writeDirectGeneratedWriteRunStart( + StringBuffer output, + List<_GeneratedFieldSpec> fields, + _DirectGeneratedPrimitiveRun run, + String indent, + ) { + output.writeln( + '${indent}var offset${run.start} = bufferReserveBytes(buffer, ${run.bytes});', + ); + if (_directGeneratedRunUsesBytes(fields, run)) { + output.writeln('${indent}final bytes${run.start} = bufferBytes(buffer);'); + } + if (_directGeneratedRunUsesView(fields, run)) { + output + .writeln('${indent}final view${run.start} = bufferByteData(buffer);'); + } + } + + void _writeDirectGeneratedReadRunStart( + StringBuffer output, + List<_GeneratedFieldSpec> fields, + _DirectGeneratedPrimitiveRun run, + String indent, + ) { + output.writeln( + '${indent}var offset${run.start} = bufferReaderIndex(buffer);', + ); + if (_directGeneratedRunUsesBytes(fields, run)) { + output.writeln('${indent}final bytes${run.start} = bufferBytes(buffer);'); + } + if (_directGeneratedRunUsesView(fields, run)) { + output + .writeln('${indent}final view${run.start} = bufferByteData(buffer);'); + } + } + + void _writeDirectGeneratedWriteRunEnd( + StringBuffer output, + _DirectGeneratedPrimitiveRun run, + String indent, + ) { + output.writeln( + '${indent}bufferSetWriterIndex(buffer, offset${run.start});', + ); + } + + void _writeDirectGeneratedReadRunEnd( + StringBuffer output, + _DirectGeneratedPrimitiveRun run, + String indent, + ) { + output.writeln( + '${indent}bufferSetReaderIndex(buffer, offset${run.start});', + ); + } + + void _writeDirectGeneratedBufferWriteStatement( + StringBuffer output, + _GeneratedFieldSpec field, + int runStart, + int fieldIndex, + String valueExpression, + String indent, + ) { + final offset = 'offset$runStart'; + final bytes = 'bytes$runStart'; + final view = 'view$runStart'; + final scalar = _directGeneratedScalarExpression(field, valueExpression); + switch (field.fieldType.typeId) { + case TypeIds.boolType: + output + ..writeln('$indent$bytes[$offset] = $valueExpression ? 1 : 0;') + ..writeln('$indent$offset += 1;'); + case TypeIds.int8: + output + ..writeln('$indent$view.setInt8($offset, $scalar);') + ..writeln('$indent$offset += 1;'); + case TypeIds.uint8: + output + ..writeln('$indent$view.setUint8($offset, $scalar);') + ..writeln('$indent$offset += 1;'); + case TypeIds.int16: + output + ..writeln( + '$indent$view.setInt16($offset, $scalar, generatedLittleEndian);', + ) + ..writeln('$indent$offset += 2;'); + case TypeIds.uint16: + output + ..writeln( + '$indent$view.setUint16($offset, $scalar, generatedLittleEndian);', + ) + ..writeln('$indent$offset += 2;'); + case TypeIds.int32: + output + ..writeln( + '$indent$view.setInt32($offset, $scalar, generatedLittleEndian);', + ) + ..writeln('$indent$offset += 4;'); + case TypeIds.uint32: + output + ..writeln( + '$indent$view.setUint32($offset, $scalar, generatedLittleEndian);', + ) + ..writeln('$indent$offset += 4;'); + case TypeIds.float16: + output + ..writeln( + '$indent$view.setUint16($offset, $valueExpression.toBits(), generatedLittleEndian);', + ) + ..writeln('$indent$offset += 2;'); + case TypeIds.bfloat16: + output + ..writeln( + '$indent$view.setUint16($offset, $valueExpression.toBits(), generatedLittleEndian);', + ) + ..writeln('$indent$offset += 2;'); + case TypeIds.float32: + output + ..writeln( + '$indent$view.setFloat32($offset, $scalar, generatedLittleEndian);', + ) + ..writeln('$indent$offset += 4;'); + case TypeIds.float64: + output + ..writeln( + '$indent$view.setFloat64($offset, $scalar, generatedLittleEndian);', + ) + ..writeln('$indent$offset += 8;'); + case TypeIds.varInt32: + final checked = 'value$fieldIndex'; + final remaining = 'remaining$fieldIndex'; + output + ..writeln('$indent final $checked = $scalar;') + ..writeln( + '$indent var $remaining = (($checked << 1) ^ ($checked >> 31)).toUnsigned(32);', + ) + ..writeln('$indent while ($remaining >= 0x80) {') + ..writeln('$indent $bytes[$offset] = ($remaining & 0x7f) | 0x80;') + ..writeln('$indent $offset += 1;') + ..writeln('$indent $remaining >>>= 7;') + ..writeln('$indent }') + ..writeln('$indent $bytes[$offset] = $remaining;') + ..writeln('$indent $offset += 1;'); + case TypeIds.varUint32: + final remaining = 'remaining$fieldIndex'; + output + ..writeln('$indent var $remaining = $scalar;') + ..writeln('$indent while ($remaining >= 0x80) {') + ..writeln('$indent $bytes[$offset] = ($remaining & 0x7f) | 0x80;') + ..writeln('$indent $offset += 1;') + ..writeln('$indent $remaining >>>= 7;') + ..writeln('$indent }') + ..writeln('$indent $bytes[$offset] = $remaining;') + ..writeln('$indent $offset += 1;'); + default: + throw StateError( + 'Unsupported generated direct buffer write fast path for ${field.name}.', + ); + } + } + + void _writeDirectGeneratedBufferReadStatement( + StringBuffer output, + _GeneratedFieldSpec field, + int runStart, + int fieldIndex, + String target, + String indent, + ) { + final offset = 'offset$runStart'; + final bytes = 'bytes$runStart'; + final view = 'view$runStart'; + switch (field.fieldType.typeId) { + case TypeIds.boolType: + output + ..writeln('$indent$target = $bytes[$offset] != 0;') + ..writeln('$indent$offset += 1;'); + case TypeIds.int8: + output + ..writeln('$indent$target = $view.getInt8($offset);') + ..writeln('$indent$offset += 1;'); + case TypeIds.uint8: + output + ..writeln('$indent$target = $view.getUint8($offset);') + ..writeln('$indent$offset += 1;'); + case TypeIds.int16: + output + ..writeln( + '$indent$target = $view.getInt16($offset, generatedLittleEndian);', + ) + ..writeln('$indent$offset += 2;'); + case TypeIds.uint16: + output + ..writeln( + '$indent$target = $view.getUint16($offset, generatedLittleEndian);', + ) + ..writeln('$indent$offset += 2;'); + case TypeIds.int32: + output + ..writeln( + '$indent$target = $view.getInt32($offset, generatedLittleEndian);', + ) + ..writeln('$indent$offset += 4;'); + case TypeIds.uint32: + output + ..writeln( + '$indent$target = $view.getUint32($offset, generatedLittleEndian);', + ) + ..writeln('$indent$offset += 4;'); + case TypeIds.float16: + output + ..writeln( + '$indent$target = Float16.fromBits($view.getUint16($offset, generatedLittleEndian));', + ) + ..writeln('$indent$offset += 2;'); + case TypeIds.bfloat16: + output + ..writeln( + '$indent$target = Bfloat16.fromBits($view.getUint16($offset, generatedLittleEndian));', + ) + ..writeln('$indent$offset += 2;'); + case TypeIds.float32: + final value = '$view.getFloat32($offset, generatedLittleEndian)'; + output + ..writeln( + field.type.isDartCoreDouble + ? '$indent$target = $value;' + : '$indent$target = Float32($value);', + ) + ..writeln('$indent$offset += 4;'); + case TypeIds.float64: + output + ..writeln( + '$indent$target = $view.getFloat64($offset, generatedLittleEndian);', + ) + ..writeln('$indent$offset += 8;'); + case TypeIds.varInt32: + final result = 'result$fieldIndex'; + _writeDirectGeneratedVarUint32Read( + output, result, bytes, offset, indent); + output.writeln( + '$indent$target = (($result >>> 1) ^ -($result & 1)).toSigned(32);', + ); + case TypeIds.varUint32: + final result = 'result$fieldIndex'; + _writeDirectGeneratedVarUint32Read( + output, result, bytes, offset, indent); + output.writeln('$indent$target = $result;'); + default: + throw StateError( + 'Unsupported generated direct buffer read fast path for ${field.name}.', + ); + } + } + + void _writeDirectGeneratedVarUint32Read( + StringBuffer output, + String result, + String bytes, + String offset, + String indent, + ) { + final shift = '${result}Shift'; + final byte = '${result}Byte'; + output + ..writeln('$indent var $shift = 0;') + ..writeln('$indent var $result = 0;') + ..writeln('$indent while (true) {') + ..writeln('$indent final $byte = $bytes[$offset];') + ..writeln('$indent $offset += 1;') + ..writeln('$indent $result |= ($byte & 0x7f) << $shift;') + ..writeln('$indent if (($byte & 0x80) == 0) {') + ..writeln('$indent break;') + ..writeln('$indent }') + ..writeln('$indent $shift += 7;') + ..writeln('$indent }'); + } + String _directGeneratedWriteStatement( _GeneratedFieldSpec field, String valueExpression, @@ -1428,89 +1818,6 @@ GeneratedFieldType( } } - String _directGeneratedCursorWriteStatement( - _GeneratedFieldSpec field, - String cursorExpression, - String valueExpression, - ) { - switch (field.fieldType.typeId) { - case TypeIds.boolType: - return '$cursorExpression.writeBool($valueExpression)'; - case TypeIds.int8: - return '$cursorExpression.writeByte(${_directGeneratedScalarExpression(field, valueExpression)})'; - case TypeIds.int16: - return '$cursorExpression.writeInt16(${_directGeneratedScalarExpression(field, valueExpression)})'; - case TypeIds.int32: - return '$cursorExpression.writeInt32(${_directGeneratedScalarExpression(field, valueExpression)})'; - case TypeIds.varInt32: - return '$cursorExpression.writeVarInt32(${_directGeneratedScalarExpression(field, valueExpression)})'; - case TypeIds.int64: - if (field.type.isDartCoreInt) { - return '$cursorExpression.writeInt64FromInt($valueExpression)'; - } - return '$cursorExpression.writeInt64(${_directGeneratedScalarExpression(field, valueExpression)})'; - case TypeIds.varInt64: - if (field.type.isDartCoreInt) { - return '$cursorExpression.writeVarInt64FromInt($valueExpression)'; - } - return '$cursorExpression.writeVarInt64(${_directGeneratedScalarExpression(field, valueExpression)})'; - case TypeIds.taggedInt64: - if (field.type.isDartCoreInt) { - return '$cursorExpression.writeTaggedInt64FromInt($valueExpression)'; - } - return '$cursorExpression.writeTaggedInt64(${_directGeneratedScalarExpression(field, valueExpression)})'; - case TypeIds.uint8: - return '$cursorExpression.writeUint8(${_directGeneratedScalarExpression(field, valueExpression)})'; - case TypeIds.uint16: - return '$cursorExpression.writeUint16(${_directGeneratedScalarExpression(field, valueExpression)})'; - case TypeIds.uint32: - return '$cursorExpression.writeUint32(${_directGeneratedScalarExpression(field, valueExpression)})'; - case TypeIds.varUint32: - return '$cursorExpression.writeVarUint32(${_directGeneratedScalarExpression(field, valueExpression)})'; - case TypeIds.uint64: - if (field.type.isDartCoreInt) { - return '$cursorExpression.writeUint64FromInt($valueExpression)'; - } - return '$cursorExpression.writeUint64(${_directGeneratedScalarExpression(field, valueExpression)})'; - case TypeIds.varUint64: - if (field.type.isDartCoreInt) { - return '$cursorExpression.writeVarUint64FromInt($valueExpression)'; - } - return '$cursorExpression.writeVarUint64(${_directGeneratedScalarExpression(field, valueExpression)})'; - case TypeIds.taggedUint64: - if (field.type.isDartCoreInt) { - return '$cursorExpression.writeTaggedUint64FromInt($valueExpression)'; - } - return '$cursorExpression.writeTaggedUint64(${_directGeneratedScalarExpression(field, valueExpression)})'; - case TypeIds.float16: - return '$cursorExpression.writeFloat16($valueExpression)'; - case TypeIds.bfloat16: - return '$cursorExpression.writeBfloat16($valueExpression)'; - case TypeIds.float32: - return '$cursorExpression.writeFloat32(${_directGeneratedScalarExpression(field, valueExpression)})'; - case TypeIds.float64: - return '$cursorExpression.writeFloat64(${_directGeneratedScalarExpression(field, valueExpression)})'; - case TypeIds.date: - return '$cursorExpression.writeVarInt64($valueExpression.toEpochDay())'; - case TypeIds.duration: - return '$cursorExpression.writeVarInt64(generatedDurationWireSeconds($valueExpression)); $cursorExpression.writeInt32(generatedDurationWireNanoseconds($valueExpression))'; - case TypeIds.timestamp: - return _isDateTimeType(field.type) - ? '$cursorExpression.writeInt64(generatedDateTimeWireSeconds($valueExpression)); $cursorExpression.writeUint32(generatedDateTimeWireNanoseconds($valueExpression))' - : '$cursorExpression.writeInt64($valueExpression.seconds); $cursorExpression.writeUint32(generatedTimestampWireNanoseconds($valueExpression))'; - case TypeIds.enumById: - return _enumCursorWriteExpression( - field.type, - cursorExpression, - valueExpression, - ); - default: - throw StateError( - 'Unsupported generated direct cursor write fast path for ${field.name}.', - ); - } - } - String _directGeneratedReadExpression(_GeneratedFieldSpec field) { switch (field.fieldType.typeId) { case TypeIds.boolType: @@ -1606,7 +1913,7 @@ GeneratedFieldType( case TypeIds.float64Array: return 'readGeneratedTypedArrayValue(context, 8, (bytes) => bytes.buffer.asFloat64List(bytes.offsetInBytes, bytes.lengthInBytes ~/ 8))'; case TypeIds.enumById: - return _enumReadExpression(field.type, 'context'); + return _enumReadExpression(field.type, 'buffer'); default: throw StateError( 'Unsupported generated direct read fast path for ${field.name}.', @@ -1614,80 +1921,6 @@ GeneratedFieldType( } } - String _directGeneratedCursorReadExpression( - _GeneratedFieldSpec field, - String cursorExpression, - ) { - switch (field.fieldType.typeId) { - case TypeIds.boolType: - return '$cursorExpression.readBool()'; - case TypeIds.int8: - return '$cursorExpression.readByte()'; - case TypeIds.int16: - return '$cursorExpression.readInt16()'; - case TypeIds.int32: - return '$cursorExpression.readInt32()'; - case TypeIds.varInt32: - return '$cursorExpression.readVarInt32()'; - case TypeIds.int64: - return field.type.isDartCoreInt - ? '$cursorExpression.readInt64AsInt()' - : '$cursorExpression.readInt64()'; - case TypeIds.varInt64: - return field.type.isDartCoreInt - ? '$cursorExpression.readVarInt64AsInt()' - : '$cursorExpression.readVarInt64()'; - case TypeIds.taggedInt64: - return field.type.isDartCoreInt - ? '$cursorExpression.readTaggedInt64AsInt()' - : '$cursorExpression.readTaggedInt64()'; - case TypeIds.uint8: - return '$cursorExpression.readUint8()'; - case TypeIds.uint16: - return '$cursorExpression.readUint16()'; - case TypeIds.uint32: - return '$cursorExpression.readUint32()'; - case TypeIds.varUint32: - return '$cursorExpression.readVarUint32()'; - case TypeIds.uint64: - return field.type.isDartCoreInt - ? '$cursorExpression.readUint64AsInt()' - : '$cursorExpression.readUint64()'; - case TypeIds.varUint64: - return field.type.isDartCoreInt - ? '$cursorExpression.readVarUint64AsInt()' - : '$cursorExpression.readVarUint64()'; - case TypeIds.taggedUint64: - return field.type.isDartCoreInt - ? '$cursorExpression.readTaggedUint64AsInt()' - : '$cursorExpression.readTaggedUint64()'; - case TypeIds.float16: - return '$cursorExpression.readFloat16()'; - case TypeIds.bfloat16: - return '$cursorExpression.readBfloat16()'; - case TypeIds.float32: - return field.type.isDartCoreDouble - ? '$cursorExpression.readFloat32()' - : 'Float32($cursorExpression.readFloat32())'; - case TypeIds.float64: - return '$cursorExpression.readFloat64()'; - case TypeIds.date: - return 'LocalDate.fromEpochDay($cursorExpression.readVarInt64())'; - case TypeIds.duration: - return 'readGeneratedDurationFromWire($cursorExpression.readVarInt64(), $cursorExpression.readInt32())'; - case TypeIds.timestamp: - return _isDateTimeType(field.type) - ? 'readGeneratedDateTimeFromWire($cursorExpression.readInt64(), $cursorExpression.readUint32())' - : 'readGeneratedTimestampFromWire($cursorExpression.readInt64(), $cursorExpression.readUint32())'; - case TypeIds.enumById: - return _enumCursorReadExpression(field.type, cursorExpression); - default: - throw StateError( - 'Unsupported generated direct cursor read fast path for ${field.name}.', - ); - } - } - String _directGeneratedTypedContainerReadExpression( String structName, _GeneratedFieldSpec field, @@ -1723,7 +1956,7 @@ GeneratedFieldType( case TypeIds.uint64: case TypeIds.varUint64: case TypeIds.taggedUint64: - return 'Uint64($valueExpression)'; + return 'generatedCheckedUint64Int($valueExpression)'; default: return _checkedGeneratedScalarExpression( field.fieldType.typeId, valueExpression); @@ -2823,17 +3056,6 @@ GeneratedFieldType( return 'buffer.writeVarUint32($valueExpression.index)'; } - String _enumCursorWriteExpression( - DartType type, - String cursorExpression, - String valueExpression, - ) { - if (_enumUsesRawValue(type)) { - return '$cursorExpression.writeVarUint32($valueExpression.rawValue)'; - } - return '$cursorExpression.writeVarUint32($valueExpression.index)'; - } - String _enumReadExpression(DartType type, String contextExpression) { final typeDisplay = _typeReferenceLiteral(type); if (_enumUsesRawValue(type)) { @@ -2842,14 +3064,6 @@ GeneratedFieldType( return '$typeDisplay.values[$contextExpression.readVarUint32()]'; } - String _enumCursorReadExpression(DartType type, String cursorExpression) { - final typeDisplay = _typeReferenceLiteral(type); - if (_enumUsesRawValue(type)) { - return '$typeDisplay.fromRawValue($cursorExpression.readVarUint32())'; - } - return '$typeDisplay.values[$cursorExpression.readVarUint32()]'; - } - bool _sameType(DartType left, DartType right) => _typeLiteral(_withoutNullability(left)) == _typeLiteral(_withoutNullability(right)); @@ -2978,6 +3192,50 @@ GeneratedFieldType( } return buffer.toString(); } + + bool _structNeedsEarlyReadReference(_GeneratedStructSpec structSpec) { + for (final field in structSpec.fields) { + if (_fieldTypeNeedsEarlyReadReference(field.fieldType)) { + return true; + } + } + return false; + } + + bool _fieldTypeNeedsEarlyReadReference(_GeneratedFieldTypeSpec fieldType) { + if (fieldType.ref) { + return true; + } + for (final argument in fieldType.arguments) { + if (_fieldTypeNeedsEarlyReadReference(argument)) { + return true; + } + } + return false; + } + + bool _structUsesNestedTypeDefinitions(_GeneratedStructSpec structSpec) { + for (final field in structSpec.fields) { + if (_fieldTypeUsesNestedTypeDefinitions(field.fieldType)) { + return true; + } + } + return false; + } + + bool _fieldTypeUsesNestedTypeDefinitions( + _GeneratedFieldTypeSpec fieldType, + ) { + if (fieldType.dynamic == true || TypeIds.isUserType(fieldType.typeId)) { + return true; + } + for (final argument in fieldType.arguments) { + if (_fieldTypeUsesNestedTypeDefinitions(argument)) { + return true; + } + } + return false; + } } final class _GeneratedEnumSpec { @@ -3033,12 +3291,12 @@ final class _GeneratedFieldSpec { String get localName => '_${name}Value'; } -final class _DirectGeneratedWriteReservationRun { +final class _DirectGeneratedPrimitiveRun { final int start; final int end; final int bytes; - const _DirectGeneratedWriteReservationRun(this.start, this.end, this.bytes); + const _DirectGeneratedPrimitiveRun(this.start, this.end, this.bytes); } final class _GeneratedFieldTypeSpec { diff --git a/dart/packages/fory/lib/src/codegen/generated_cursor.dart b/dart/packages/fory/lib/src/codegen/generated_cursor.dart deleted file mode 100644 index 44816f1189..0000000000 --- a/dart/packages/fory/lib/src/codegen/generated_cursor.dart +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export 'generated_cursor_native.dart' - if (dart.library.js_interop) 'generated_cursor_web.dart'; diff --git a/dart/packages/fory/lib/src/codegen/generated_cursor_mixin.dart b/dart/packages/fory/lib/src/codegen/generated_cursor_mixin.dart deleted file mode 100644 index d8aa225336..0000000000 --- a/dart/packages/fory/lib/src/codegen/generated_cursor_mixin.dart +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// ignore_for_file: use_string_in_part_of_directives - -part of fory.src.codegen.generated_cursor; - -mixin _GeneratedWriteCursorMixin { - late final Buffer _buffer; - late final Uint8List _bytes; - late final ByteData _view; - int _offset = 0; - - void _initWriteCursor(Buffer buffer, int maxBytes) { - final start = bufferReserveBytes(buffer, maxBytes); - _buffer = buffer; - _bytes = bufferBytes(buffer); - _view = bufferByteData(buffer); - _offset = start; - } - - void finish() { - bufferSetWriterIndex(_buffer, _offset); - } - - @pragma('vm:prefer-inline') - void writeBool(bool value) { - _bytes[_offset] = value ? 1 : 0; - _offset += 1; - } - - @pragma('vm:prefer-inline') - void writeByte(int value) { - _view.setInt8(_offset, value); - _offset += 1; - } - - @pragma('vm:prefer-inline') - void writeUint8(int value) { - _view.setUint8(_offset, value); - _offset += 1; - } - - @pragma('vm:prefer-inline') - void writeInt16(int value) { - _view.setInt16(_offset, value, Endian.little); - _offset += 2; - } - - @pragma('vm:prefer-inline') - void writeUint16(int value) { - _view.setUint16(_offset, value, Endian.little); - _offset += 2; - } - - @pragma('vm:prefer-inline') - void writeInt32(int value) { - _view.setInt32(_offset, value, Endian.little); - _offset += 4; - } - - @pragma('vm:prefer-inline') - void writeUint32(int value) { - _view.setUint32(_offset, value, Endian.little); - _offset += 4; - } - - @pragma('vm:prefer-inline') - void writeFloat16(Float16 value) { - writeUint16(value.toBits()); - } - - @pragma('vm:prefer-inline') - void writeBfloat16(Bfloat16 value) { - writeUint16(value.toBits()); - } - - @pragma('vm:prefer-inline') - void writeFloat32(double value) { - _view.setFloat32(_offset, value, Endian.little); - _offset += 4; - } - - @pragma('vm:prefer-inline') - void writeFloat64(double value) { - _view.setFloat64(_offset, value, Endian.little); - _offset += 8; - } - - @pragma('vm:prefer-inline') - void writeVarUint32(int value) { - var remaining = value; - while (remaining >= 0x80) { - _bytes[_offset] = (remaining & 0x7f) | 0x80; - _offset += 1; - remaining >>>= 7; - } - _bytes[_offset] = remaining; - _offset += 1; - } - - @pragma('vm:prefer-inline') - void writeVarInt32(int value) { - writeVarUint32(((value << 1) ^ (value >> 31)).toUnsigned(32)); - } - - void writeVarUint64(Uint64 value); -} - -mixin _GeneratedReadCursorMixin { - late final Buffer _buffer; - late final ByteData _view; - int _offset = 0; - - void _initReadCursor(Buffer buffer) { - _buffer = buffer; - _view = bufferByteData(buffer); - _offset = bufferReaderIndex(buffer); - } - - void finish() { - bufferSetReaderIndex(_buffer, _offset); - } - - @pragma('vm:prefer-inline') - bool readBool() => readUint8() != 0; - - @pragma('vm:prefer-inline') - int readByte() { - final value = _view.getInt8(_offset); - _offset += 1; - return value; - } - - @pragma('vm:prefer-inline') - int readUint8() { - final value = _view.getUint8(_offset); - _offset += 1; - return value; - } - - @pragma('vm:prefer-inline') - int readInt16() { - final value = _view.getInt16(_offset, Endian.little); - _offset += 2; - return value; - } - - @pragma('vm:prefer-inline') - int readUint16() { - final value = _view.getUint16(_offset, Endian.little); - _offset += 2; - return value; - } - - @pragma('vm:prefer-inline') - int readInt32() { - final value = _view.getInt32(_offset, Endian.little); - _offset += 4; - return value; - } - - @pragma('vm:prefer-inline') - int readUint32() { - final value = _view.getUint32(_offset, Endian.little); - _offset += 4; - return value; - } - - @pragma('vm:prefer-inline') - Float16 readFloat16() => Float16.fromBits(readUint16()); - - @pragma('vm:prefer-inline') - Bfloat16 readBfloat16() => Bfloat16.fromBits(readUint16()); - - @pragma('vm:prefer-inline') - double readFloat32() { - final value = _view.getFloat32(_offset, Endian.little); - _offset += 4; - return value; - } - - @pragma('vm:prefer-inline') - double readFloat64() { - final value = _view.getFloat64(_offset, Endian.little); - _offset += 8; - return value; - } - - @pragma('vm:prefer-inline') - int readVarUint32() { - var shift = 0; - var result = 0; - while (true) { - final byte = readUint8(); - result |= (byte & 0x7f) << shift; - if ((byte & 0x80) == 0) { - return result; - } - shift += 7; - } - } - - @pragma('vm:prefer-inline') - int readVarInt32() { - final value = readVarUint32(); - return ((value >>> 1) ^ -(value & 1)).toSigned(32); - } - - Uint64 readVarUint64(); -} diff --git a/dart/packages/fory/lib/src/codegen/generated_cursor_native.dart b/dart/packages/fory/lib/src/codegen/generated_cursor_native.dart deleted file mode 100644 index 482d31d15c..0000000000 --- a/dart/packages/fory/lib/src/codegen/generated_cursor_native.dart +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// ignore_for_file: unnecessary_library_name - -library fory.src.codegen.generated_cursor; - -import 'dart:typed_data'; - -import 'package:meta/meta.dart'; - -import 'package:fory/src/memory/buffer.dart'; -import 'package:fory/src/types/bfloat16.dart'; -import 'package:fory/src/types/float16.dart'; -import 'package:fory/src/types/int64.dart'; -import 'package:fory/src/types/uint64.dart'; - -part 'generated_cursor_mixin.dart'; - -@internal -final class GeneratedWriteCursor with _GeneratedWriteCursorMixin { - GeneratedWriteCursor._(); - - factory GeneratedWriteCursor.reserve(Buffer buffer, int maxBytes) { - return GeneratedWriteCursor._().._initWriteCursor(buffer, maxBytes); - } - - @pragma('vm:prefer-inline') - void writeInt64(Int64 value) { - _view.setInt64(_offset, value.toInt(), Endian.little); - _offset += 8; - } - - @pragma('vm:prefer-inline') - void writeInt64FromInt(int value) { - _view.setInt64(_offset, value, Endian.little); - _offset += 8; - } - - @pragma('vm:prefer-inline') - void writeUint64(Uint64 value) { - _view.setInt64(_offset, value.value, Endian.little); - _offset += 8; - } - - @pragma('vm:prefer-inline') - void writeUint64FromInt(int value) { - _view.setInt64(_offset, value.toSigned(64), Endian.little); - _offset += 8; - } - - @pragma('vm:prefer-inline') - @override - void writeVarUint64(Uint64 value) { - _writeVarUint64Int(value.value); - } - - @pragma('vm:prefer-inline') - void writeVarUint64FromInt(int value) { - _writeVarUint64Int(value.toSigned(64)); - } - - @pragma('vm:prefer-inline') - void writeVarInt64(Int64 value) { - _writeVarUint64Int((value << 1) ^ (value >> 63)); - } - - @pragma('vm:prefer-inline') - void writeVarInt64FromInt(int value) { - _writeVarUint64Int((value << 1) ^ (value >> 63)); - } - - @pragma('vm:prefer-inline') - void writeTaggedInt64(Int64 value) { - if (value >= -0x40000000 && value <= 0x3fffffff) { - writeInt32((value.toInt() << 1).toSigned(32)); - return; - } - writeUint8(0x01); - writeInt64(value); - } - - @pragma('vm:prefer-inline') - void writeTaggedInt64FromInt(int value) { - if (value >= -0x40000000 && value <= 0x3fffffff) { - writeInt32((value << 1).toSigned(32)); - return; - } - writeUint8(0x01); - writeInt64FromInt(value); - } - - @pragma('vm:prefer-inline') - void writeTaggedUint64(Uint64 value) { - if (value >= 0 && value <= 0x7fffffff) { - writeInt32(value.toInt() << 1); - return; - } - writeUint8(0x01); - writeUint64(value); - } - - @pragma('vm:prefer-inline') - void writeTaggedUint64FromInt(int value) { - if (value >= 0 && value <= 0x7fffffff) { - writeInt32(value << 1); - return; - } - writeUint8(0x01); - writeUint64FromInt(value); - } - - @pragma('vm:prefer-inline') - void _writeVarUint64Int(int value) { - var remaining = value; - for (var index = 0; index < 8; index += 1) { - final chunk = remaining & 0x7f; - remaining >>>= 7; - if (remaining == 0) { - _bytes[_offset] = chunk; - _offset += 1; - return; - } - _bytes[_offset] = chunk | 0x80; - _offset += 1; - } - _bytes[_offset] = remaining & 0xff; - _offset += 1; - } -} - -@internal -final class GeneratedReadCursor with _GeneratedReadCursorMixin { - GeneratedReadCursor._(); - - factory GeneratedReadCursor.start(Buffer buffer) { - return GeneratedReadCursor._().._initReadCursor(buffer); - } - - @pragma('vm:prefer-inline') - Int64 readInt64() { - final value = Int64(_view.getInt64(_offset, Endian.little)); - _offset += 8; - return value; - } - - @pragma('vm:prefer-inline') - int readInt64AsInt() { - final value = _view.getInt64(_offset, Endian.little); - _offset += 8; - return value; - } - - @pragma('vm:prefer-inline') - Uint64 readUint64() { - final value = Uint64(_view.getInt64(_offset, Endian.little)); - _offset += 8; - return value; - } - - @pragma('vm:prefer-inline') - int readUint64AsInt() { - final value = _view.getUint64(_offset, Endian.little); - if ((_view.getUint32(_offset + 4, Endian.little) & 0x80000000) != 0) { - throw StateError( - 'Uint64 value $value is not representable as a native int.', - ); - } - _offset += 8; - return value; - } - - @pragma('vm:prefer-inline') - @override - Uint64 readVarUint64() { - var shift = 0; - var result = Uint64(0); - while (shift < 56) { - final byte = readUint8(); - result = result | (Uint64(byte & 0x7f) << shift); - if ((byte & 0x80) == 0) { - return result; - } - shift += 7; - } - return result | (Uint64(readUint8()) << 56); - } - - @pragma('vm:prefer-inline') - int readVarUint64AsInt() { - var shift = 0; - var result = 0; - while (shift < 56) { - final byte = readUint8(); - result |= (byte & 0x7f) << shift; - if ((byte & 0x80) == 0) { - return result; - } - shift += 7; - } - final byte = readUint8(); - final value = result | (byte << 56); - if ((byte & 0x80) != 0) { - throw StateError( - 'Uint64 value $value is not representable as a native int.', - ); - } - return value; - } - - @pragma('vm:prefer-inline') - Int64 readVarInt64() { - final encoded = readVarUint64(); - return Int64((encoded >>> 1) ^ -(encoded & 1)); - } - - @pragma('vm:prefer-inline') - int readVarInt64AsInt() { - var shift = 0; - var encoded = 0; - while (shift < 56) { - final byte = readUint8(); - encoded |= (byte & 0x7f) << shift; - if ((byte & 0x80) == 0) { - return (encoded >>> 1) ^ -(encoded & 1); - } - shift += 7; - } - encoded |= readUint8() << 56; - return (encoded >>> 1) ^ -(encoded & 1); - } - - @pragma('vm:prefer-inline') - Int64 readTaggedInt64() { - final readIndex = _offset; - final first = _view.getInt32(readIndex, Endian.little); - if ((first & 1) == 0) { - _offset = readIndex + 4; - return Int64(first.toSigned(32) ~/ 2); - } - final value = Int64(_view.getInt64(readIndex + 1, Endian.little)); - _offset = readIndex + 9; - return value; - } - - @pragma('vm:prefer-inline') - int readTaggedInt64AsInt() { - final readIndex = _offset; - final first = _view.getInt32(readIndex, Endian.little); - if ((first & 1) == 0) { - _offset = readIndex + 4; - return first.toSigned(32) ~/ 2; - } - final value = _view.getInt64(readIndex + 1, Endian.little); - _offset = readIndex + 9; - return value; - } - - @pragma('vm:prefer-inline') - Uint64 readTaggedUint64() { - final readIndex = _offset; - final first = _view.getUint32(readIndex, Endian.little); - if ((first & 1) == 0) { - _offset = readIndex + 4; - return Uint64(first >>> 1); - } - final value = Uint64(_view.getInt64(readIndex + 1, Endian.little)); - _offset = readIndex + 9; - return value; - } - - @pragma('vm:prefer-inline') - int readTaggedUint64AsInt() { - final readIndex = _offset; - final first = _view.getUint32(readIndex, Endian.little); - if ((first & 1) == 0) { - _offset = readIndex + 4; - return first >>> 1; - } - final value = _view.getUint64(readIndex + 1, Endian.little); - if ((_view.getUint32(readIndex + 5, Endian.little) & 0x80000000) != 0) { - throw StateError( - 'Uint64 value $value is not representable as a native int.', - ); - } - _offset = readIndex + 9; - return value; - } -} diff --git a/dart/packages/fory/lib/src/codegen/generated_cursor_web.dart b/dart/packages/fory/lib/src/codegen/generated_cursor_web.dart deleted file mode 100644 index d58a518769..0000000000 --- a/dart/packages/fory/lib/src/codegen/generated_cursor_web.dart +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// ignore_for_file: unnecessary_library_name - -library fory.src.codegen.generated_cursor; - -import 'dart:typed_data'; - -import 'package:meta/meta.dart'; - -import 'package:fory/src/memory/buffer.dart'; -import 'package:fory/src/types/bfloat16.dart'; -import 'package:fory/src/types/float16.dart'; -import 'package:fory/src/types/int64.dart'; -import 'package:fory/src/types/uint64.dart'; - -part 'generated_cursor_mixin.dart'; - -const int _jsSafeIntMax = 9007199254740991; -const int _jsSafeIntMin = -9007199254740991; - -@internal -final class GeneratedWriteCursor with _GeneratedWriteCursorMixin { - GeneratedWriteCursor._(); - - factory GeneratedWriteCursor.reserve(Buffer buffer, int maxBytes) { - return GeneratedWriteCursor._().._initWriteCursor(buffer, maxBytes); - } - - @pragma('vm:prefer-inline') - void writeInt64(Int64 value) { - _writeInt64Words(_offset, value); - _offset += 8; - } - - @pragma('vm:prefer-inline') - void writeInt64FromInt(int value) { - _writeInt64Words(_offset, _int64FromInt(value)); - _offset += 8; - } - - @pragma('vm:prefer-inline') - void writeUint64(Uint64 value) { - _writeUint64Words(_offset, value); - _offset += 8; - } - - @pragma('vm:prefer-inline') - void writeUint64FromInt(int value) { - _checkUint64IntRange(value); - _writeUint64Words(_offset, Uint64(value)); - _offset += 8; - } - - @pragma('vm:prefer-inline') - @override - void writeVarUint64(Uint64 value) { - var remaining = value; - for (var shift = 0; shift < 56 && remaining > 0x7f; shift += 7) { - _bytes[_offset] = (remaining.low32 & 0x7f) | 0x80; - _offset += 1; - remaining = remaining >> 7; - } - _bytes[_offset] = remaining.toInt(); - _offset += 1; - } - - @pragma('vm:prefer-inline') - void writeVarUint64FromInt(int value) { - _checkUint64IntRange(value); - writeVarUint64(Uint64(value)); - } - - @pragma('vm:prefer-inline') - void writeVarInt64(Int64 value) { - writeVarUint64(_zigZagEncodeInt64(value)); - } - - @pragma('vm:prefer-inline') - void writeVarInt64FromInt(int value) { - writeVarInt64(_int64FromInt(value)); - } - - @pragma('vm:prefer-inline') - void writeTaggedInt64(Int64 value) { - if (value >= -0x40000000 && value <= 0x3fffffff) { - writeInt32((value.toInt() << 1).toSigned(32)); - return; - } - writeUint8(0x01); - writeInt64(value); - } - - @pragma('vm:prefer-inline') - void writeTaggedInt64FromInt(int value) { - if (value >= -0x40000000 && value <= 0x3fffffff) { - writeInt32((value << 1).toSigned(32)); - return; - } - _checkInt64IntRange(value); - writeUint8(0x01); - writeInt64FromInt(value); - } - - @pragma('vm:prefer-inline') - void writeTaggedUint64(Uint64 value) { - if (value >= 0 && value <= 0x7fffffff) { - writeInt32(value.toInt() << 1); - return; - } - writeUint8(0x01); - writeUint64(value); - } - - @pragma('vm:prefer-inline') - void writeTaggedUint64FromInt(int value) { - if (value >= 0 && value <= 0x7fffffff) { - writeInt32(value << 1); - return; - } - _checkUint64IntRange(value); - writeUint8(0x01); - writeUint64FromInt(value); - } - - @pragma('vm:prefer-inline') - void _writeInt64Words(int offset, Int64 value) { - _view.setUint32(offset, value.low32, Endian.little); - _view.setUint32(offset + 4, value.high32Unsigned, Endian.little); - } - - @pragma('vm:prefer-inline') - void _writeUint64Words(int offset, Uint64 value) { - _view.setUint32(offset, value.low32, Endian.little); - _view.setUint32(offset + 4, value.high32Unsigned, Endian.little); - } -} - -@internal -final class GeneratedReadCursor with _GeneratedReadCursorMixin { - GeneratedReadCursor._(); - - factory GeneratedReadCursor.start(Buffer buffer) { - return GeneratedReadCursor._().._initReadCursor(buffer); - } - - @pragma('vm:prefer-inline') - Int64 readInt64() { - final value = _readInt64Words(_offset); - _offset += 8; - return value; - } - - @pragma('vm:prefer-inline') - int readInt64AsInt() { - final value = _int64ToInt(_readInt64Words(_offset)); - _offset += 8; - return value; - } - - @pragma('vm:prefer-inline') - Uint64 readUint64() { - final value = _readUint64Words(_offset); - _offset += 8; - return value; - } - - @pragma('vm:prefer-inline') - int readUint64AsInt() { - final value = _readUint64Words(_offset).toInt(); - _offset += 8; - return value; - } - - @pragma('vm:prefer-inline') - @override - Uint64 readVarUint64() { - var shift = 0; - var result = Uint64(0); - while (shift < 56) { - final byte = readUint8(); - result = result | (Uint64(byte & 0x7f) << shift); - if ((byte & 0x80) == 0) { - return result; - } - shift += 7; - } - return result | (Uint64(readUint8()) << 56); - } - - @pragma('vm:prefer-inline') - int readVarUint64AsInt() { - return readVarUint64().toInt(); - } - - @pragma('vm:prefer-inline') - Int64 readVarInt64() { - return _zigZagDecodeInt64(readVarUint64()); - } - - @pragma('vm:prefer-inline') - int readVarInt64AsInt() { - return _int64ToInt(readVarInt64()); - } - - @pragma('vm:prefer-inline') - Int64 readTaggedInt64() { - final readIndex = _offset; - final first = _view.getInt32(readIndex, Endian.little); - if ((first & 1) == 0) { - _offset = readIndex + 4; - return Int64(first.toSigned(32) ~/ 2); - } - final value = _readInt64Words(readIndex + 1); - _offset = readIndex + 9; - return value; - } - - @pragma('vm:prefer-inline') - int readTaggedInt64AsInt() { - final readIndex = _offset; - final first = _view.getInt32(readIndex, Endian.little); - if ((first & 1) == 0) { - _offset = readIndex + 4; - return first.toSigned(32) ~/ 2; - } - final value = _int64ToInt(_readInt64Words(readIndex + 1)); - _offset = readIndex + 9; - return value; - } - - @pragma('vm:prefer-inline') - Uint64 readTaggedUint64() { - final readIndex = _offset; - final first = _view.getUint32(readIndex, Endian.little); - if ((first & 1) == 0) { - _offset = readIndex + 4; - return Uint64(first >>> 1); - } - final value = _readUint64Words(readIndex + 1); - _offset = readIndex + 9; - return value; - } - - @pragma('vm:prefer-inline') - int readTaggedUint64AsInt() { - final readIndex = _offset; - final first = _view.getUint32(readIndex, Endian.little); - if ((first & 1) == 0) { - _offset = readIndex + 4; - return first >>> 1; - } - final value = _readUint64Words(readIndex + 1).toInt(); - _offset = readIndex + 9; - return value; - } - - @pragma('vm:prefer-inline') - Int64 _readInt64Words(int offset) { - return Int64.fromWords( - _view.getUint32(offset, Endian.little), - _view.getInt32(offset + 4, Endian.little), - ); - } - - @pragma('vm:prefer-inline') - Uint64 _readUint64Words(int offset) { - return Uint64.fromWords( - _view.getUint32(offset, Endian.little), - _view.getUint32(offset + 4, Endian.little), - ); - } -} - -@pragma('vm:prefer-inline') -void _checkInt64IntRange(int value) { - if (value < _jsSafeIntMin || value > _jsSafeIntMax) { - throw StateError( - 'Dart int value $value is outside the JS-safe signed int64 range ' - '[$_jsSafeIntMin, $_jsSafeIntMax]. Use Int64 for full 64-bit values ' - 'on web.', - ); - } -} - -@pragma('vm:prefer-inline') -Int64 _int64FromInt(int value) { - _checkInt64IntRange(value); - return Int64(value); -} - -@pragma('vm:prefer-inline') -void _checkUint64IntRange(int value) { - if (value < 0 || value > _jsSafeIntMax) { - throw StateError( - 'Dart int value $value is outside the JS-safe unsigned uint64 int ' - 'field range [0, $_jsSafeIntMax]. Use Uint64 for full unsigned ' - '64-bit values on web.', - ); - } -} - -@pragma('vm:prefer-inline') -int _int64ToInt(Int64 value) => value.toInt(); - -@pragma('vm:prefer-inline') -Uint64 _zigZagEncodeInt64(Int64 value) { - final encoded = (value << 1) ^ (value >> 63); - return Uint64.fromWords(encoded.low32, encoded.high32Unsigned); -} - -@pragma('vm:prefer-inline') -Int64 _zigZagDecodeInt64(Uint64 encoded) { - final magnitude = encoded >> 1; - final decoded = Int64.fromWords(magnitude.low32, magnitude.high32Unsigned); - if ((encoded.low32 & 1) == 0) { - return decoded; - } - return -(decoded + 1); -} diff --git a/dart/packages/fory/lib/src/codegen/generated_registry.dart b/dart/packages/fory/lib/src/codegen/generated_registry.dart index 61368b7d4a..0b432e14c1 100644 --- a/dart/packages/fory/lib/src/codegen/generated_registry.dart +++ b/dart/packages/fory/lib/src/codegen/generated_registry.dart @@ -21,7 +21,6 @@ import 'package:meta/meta.dart'; import 'package:fory/src/meta/field_info.dart'; import 'package:fory/src/serializer/serializer.dart'; -import 'package:fory/src/serializer/struct_serializer.dart'; enum GeneratedRegistrationKind { enumType, @@ -33,18 +32,17 @@ final class GeneratedRegistration { final GeneratedRegistrationKind kind; final Serializer Function() serializerFactory; final bool evolving; + final bool needsRootRef; + final bool usesNestedTypeDefinitions; final List fields; - final GeneratedStructCompatibleFactory? compatibleFactory; - final List>? - compatibleReadersBySlot; const GeneratedRegistration({ required this.kind, required this.serializerFactory, this.evolving = true, + this.needsRootRef = false, + this.usesNestedTypeDefinitions = true, this.fields = const [], - this.compatibleFactory, - this.compatibleReadersBySlot, }); } diff --git a/dart/packages/fory/lib/src/codegen/generated_support.dart b/dart/packages/fory/lib/src/codegen/generated_support.dart index 982e3e3df8..2579887921 100644 --- a/dart/packages/fory/lib/src/codegen/generated_support.dart +++ b/dart/packages/fory/lib/src/codegen/generated_support.dart @@ -22,7 +22,15 @@ import 'dart:typed_data'; import 'package:meta/meta.dart'; import 'package:fory/fory.dart'; -export 'package:fory/src/codegen/generated_cursor.dart'; +export 'package:fory/src/memory/buffer.dart' + show + bufferByteData, + bufferBytes, + bufferReaderIndex, + bufferReserveBytes, + bufferSetReaderIndex, + bufferSetWriterIndex; +export 'package:fory/src/serializer/generated_struct_serializer.dart'; import 'package:fory/src/codegen/generated_registry.dart'; import 'package:fory/src/context/ref_writer.dart'; @@ -34,12 +42,13 @@ import 'package:fory/src/serializer/map_serializers.dart'; import 'package:fory/src/serializer/scalar_serializers.dart'; import 'package:fory/src/serializer/serialization_field_info.dart'; import 'package:fory/src/serializer/serializer_support.dart'; -import 'package:fory/src/serializer/struct_serializer.dart'; -import 'package:fory/src/serializer/struct_slots.dart'; import 'package:fory/src/serializer/time_serializers.dart'; import 'package:fory/src/serializer/typed_array_serializers.dart'; import 'package:fory/src/util/int_validation.dart'; +@internal +const Endian generatedLittleEndian = Endian.little; + @internal final class GeneratedFieldType { final Type type; @@ -75,17 +84,6 @@ final class GeneratedFieldType { } } -@internal -Object? resolveGeneratedSlotRawValue( - ReadContext context, - Object? rawValue, -) { - if (rawValue is DeferredReadRef) { - return context.getReadRef(rawValue.id); - } - return rawValue; -} - @internal final class GeneratedFieldInfo { final String name; @@ -124,31 +122,21 @@ final class GeneratedEnumRegistration { @internal typedef GeneratedStructFieldInfo = SerializationFieldInfo; -@internal -typedef GeneratedStructFieldInfoWriter = void Function( - WriteContext context, GeneratedStructFieldInfo field, T value); - -@internal -typedef GeneratedStructFieldInfoReader = void Function( - ReadContext context, T value, Object? rawValue); - @internal final class GeneratedStructRegistration { - final List> fieldWritersBySlot; - final GeneratedStructCompatibleFactory? compatibleFactory; - final List>? compatibleReadersBySlot; final Type type; final Serializer Function() serializerFactory; final bool evolving; + final bool needsRootRef; + final bool usesNestedTypeDefinitions; final List fields; GeneratedStructRegistration({ - required this.fieldWritersBySlot, - this.compatibleFactory, - this.compatibleReadersBySlot, required this.type, required this.serializerFactory, required this.evolving, + required this.needsRootRef, + required this.usesNestedTypeDefinitions, required this.fields, }); @@ -159,10 +147,6 @@ final class GeneratedStructRegistration { (index) => fields[index].toFieldInfo(), ), ); - - late final List defaultSlots = List.unmodifiable( - List.generate(fieldInfos.length, (index) => index), - ); } @internal @@ -196,29 +180,15 @@ void registerGeneratedStruct( String? namespace, String? typeName, }) { - final compatibleReadersBySlot = registration.compatibleReadersBySlot == null - ? null - : List>.unmodifiable( - registration.compatibleReadersBySlot!.map( - (reader) => ( - ReadContext context, - Object value, - Object? rawValue, - ) => - reader(context, value as T, rawValue), - ), - ); GeneratedRegistrationCatalog.remember( registration.type, GeneratedRegistration( kind: GeneratedRegistrationKind.struct, serializerFactory: registration.serializerFactory, evolving: registration.evolving, + needsRootRef: registration.needsRootRef, + usesNestedTypeDefinitions: registration.usesNestedTypeDefinitions, fields: registration.fieldInfos, - compatibleFactory: registration.compatibleFactory == null - ? null - : () => registration.compatibleFactory!() as Object, - compatibleReadersBySlot: compatibleReadersBySlot, ), ); fory.register( @@ -229,16 +199,6 @@ void registerGeneratedStruct( ); } -@internal -StructWriteSlots? generatedStructWriteSlots(WriteContext context) { - return context.structWriteSlots; -} - -@internal -StructReadSlots? generatedStructReadSlots(ReadContext context) { - return context.structReadSlots; -} - @internal void writeGeneratedBinaryValue(WriteContext context, Uint8List value) { BinarySerializer.writePayload(context, value); @@ -273,6 +233,22 @@ int generatedCheckedUint16(int value) => checkedUint16(value); @pragma('vm:prefer-inline') int generatedCheckedUint32(int value) => checkedUint32(value); +const int _generatedJsSafeUint64IntMax = 9007199254740991; +const bool _generatedIsWeb = bool.fromEnvironment('dart.library.js_interop'); + +@internal +@pragma('vm:prefer-inline') +Uint64 generatedCheckedUint64Int(int value) { + if (_generatedIsWeb && (value < 0 || value > _generatedJsSafeUint64IntMax)) { + throw StateError( + 'Dart int value $value is outside the JS-safe unsigned uint64 ' + 'int field range [0, $_generatedJsSafeUint64IntMax]. Use Uint64 for ' + 'full unsigned 64-bit values on web.', + ); + } + return Uint64(value); +} + @internal void writeGeneratedBoolArrayValue(WriteContext context, BoolList value) { final buffer = context.buffer; @@ -410,7 +386,7 @@ List buildGeneratedUnionCaseFieldInfos( fields.length, (index) => GeneratedStructFieldInfo( field: fields[index].toFieldInfo(), - slot: index, + index: index, ), growable: false, ); diff --git a/dart/packages/fory/lib/src/context/read_context.dart b/dart/packages/fory/lib/src/context/read_context.dart index 27dd038db9..6d17c68670 100644 --- a/dart/packages/fory/lib/src/context/read_context.dart +++ b/dart/packages/fory/lib/src/context/read_context.dart @@ -33,7 +33,6 @@ import 'package:fory/src/serializer/primitive_serializers.dart'; import 'package:fory/src/serializer/scalar_serializers.dart'; import 'package:fory/src/serializer/serializer.dart'; import 'package:fory/src/serializer/serializer_support.dart'; -import 'package:fory/src/serializer/struct_slots.dart'; import 'package:fory/src/serializer/time_serializers.dart'; import 'package:fory/src/serializer/typed_array_serializers.dart'; import 'package:fory/src/types/bfloat16.dart'; @@ -56,7 +55,6 @@ final class ReadContext { late Buffer _buffer; final List _sharedTypes = []; - StructReadSlots? _structReadSlots; int _depth = 0; @internal @@ -77,7 +75,6 @@ final class ReadContext { _sharedTypes.clear(); _refReader.reset(); _metaStringReader.reset(); - _structReadSlots = null; _depth = 0; } @@ -90,14 +87,6 @@ final class ReadContext { @internal RefReader get refReader => _refReader; - @internal - StructReadSlots? get structReadSlots => _structReadSlots; - - @internal - set structReadSlots(StructReadSlots? value) { - _structReadSlots = value; - } - @internal @pragma('vm:prefer-inline') TypeInfo readTypeMetaValue([ @@ -110,24 +99,7 @@ final class ReadContext { Object? readSerializerPayload( Serializer serializer, TypeInfo resolved, {required bool hasCurrentPreservedRef}) { - final int? sentinelId; - final int? sentinelDepth; - final needsSentinel = resolved.supportsRef && !hasCurrentPreservedRef; - if (needsSentinel) { - sentinelId = _refReader.preserveRefId(-1); - sentinelDepth = _refReader.preservedRefDepth; - } else { - sentinelId = null; - sentinelDepth = null; - } - final value = serializer.read(this); - if (needsSentinel && - _refReader.preservedRefDepth == sentinelDepth && - _refReader.hasPreservedRefId && - _refReader.lastPreservedRefId == sentinelId) { - _refReader.reference(null); - } - return value; + return serializer.read(this); } int get depth => _depth; @@ -137,7 +109,7 @@ final class ReadContext { void increaseDepth() { _depth += 1; if (_depth > config.maxDepth) { - throw StateError('Deserialization depth exceeded ${config.maxDepth}.'); + _throwMaxDepthExceeded(); } } @@ -147,6 +119,11 @@ final class ReadContext { _depth -= 1; } + @pragma('vm:never-inline') + Never _throwMaxDepthExceeded() { + throw StateError('Deserialization depth exceeded ${config.maxDepth}.'); + } + /// Reads a boolean value. bool readBool() => _buffer.readBool(); @@ -241,15 +218,40 @@ final class ReadContext { if (flag == RefWriter.refFlag) { return _refReader.getReadRef(); } - final resolved = _typeResolver.resolveExpectedRootWireType( - _readTypeMeta(), - ); + final expectedRootType = _typeResolver.expectedRootType(); + final typeMetaResolved = expectedRootType == null + ? _readTypeMeta() + : _typeResolver.readExpectedInitialTypeDefMeta( + _buffer, + expectedRootType, + sharedTypes: _sharedTypes, + ) ?? + _readTypeMeta(expectedRootType); + final resolved = + _typeResolver.resolveExpectedRootWireType(typeMetaResolved); final rootPreservedRefId = preservedRefId == null && flag == RefWriter.notNullValueFlag && _depth == 0 && - resolved.supportsRef + resolved.needsRootRef ? _refReader.preserveRefId() : null; + if (preservedRefId == null && + rootPreservedRefId == null && + expectedRootType != null && + identical(resolved, expectedRootType) && + resolved.kind == RegistrationKind.struct && + resolved.remoteTypeDef == null) { + _depth += 1; + if (_depth > config.maxDepth) { + _throwMaxDepthExceeded(); + } + final value = resolved.structSerializer!.readSameTypeValue( + this, + resolved, + ); + _depth -= 1; + return value; + } final value = readResolvedValue( resolved, null, @@ -280,7 +282,7 @@ final class ReadContext { final rootPreservedRefId = preservedRefId == null && flag == RefWriter.notNullValueFlag && _depth == 0 && - resolved.supportsRef + resolved.needsRootRef ? _refReader.preserveRefId() : null; final value = readResolvedValue( diff --git a/dart/packages/fory/lib/src/context/ref_reader.dart b/dart/packages/fory/lib/src/context/ref_reader.dart index 09aaaf7feb..28ee9ca0c2 100644 --- a/dart/packages/fory/lib/src/context/ref_reader.dart +++ b/dart/packages/fory/lib/src/context/ref_reader.dart @@ -39,11 +39,18 @@ final class RefReader { return flag; } + @pragma('vm:prefer-inline') int tryPreserveRefId(Buffer buffer) { - final flag = readRefOrNull(buffer); + final flag = buffer.readByte(); if (flag == RefWriter.refValueFlag) { return preserveRefId(); } + if (flag == RefWriter.refFlag) { + final id = buffer.readVarUint32(); + _resolvedId = id; + _resolved = _refs[id]; + return flag; + } return flag; } diff --git a/dart/packages/fory/lib/src/context/write_context.dart b/dart/packages/fory/lib/src/context/write_context.dart index 1c6638145d..35b04a0a42 100644 --- a/dart/packages/fory/lib/src/context/write_context.dart +++ b/dart/packages/fory/lib/src/context/write_context.dart @@ -35,7 +35,6 @@ import 'package:fory/src/serializer/collection_serializers.dart'; import 'package:fory/src/serializer/map_serializers.dart'; import 'package:fory/src/serializer/primitive_serializers.dart'; import 'package:fory/src/serializer/scalar_serializers.dart'; -import 'package:fory/src/serializer/struct_slots.dart'; import 'package:fory/src/serializer/time_serializers.dart'; import 'package:fory/src/serializer/typed_array_serializers.dart'; import 'package:fory/src/types/bfloat16.dart'; @@ -61,7 +60,6 @@ final class WriteContext { late Buffer _buffer; final LinkedHashMap _typeDefIds = LinkedHashMap.identity(); - StructWriteSlots? _structWriteSlots; bool _rootTrackRef = false; int _depth = 0; @@ -84,7 +82,6 @@ final class WriteContext { _typeDefIds.clear(); _refWriter.reset(); _metaStringWriter.reset(); - _structWriteSlots = null; _rootTrackRef = false; _depth = 0; } @@ -101,14 +98,6 @@ final class WriteContext { @internal bool get rootTrackRef => _rootTrackRef; - @internal - StructWriteSlots? get structWriteSlots => _structWriteSlots; - - @internal - set structWriteSlots(StructWriteSlots? value) { - _structWriteSlots = value; - } - int get depth => _depth; /// Records entry into one more nested write frame. @@ -214,7 +203,7 @@ final class WriteContext { return; } _refWriter.writeRefOrNull(_buffer, value, trackRef: false); - if (resolved.supportsRef) { + if (resolved.needsRootRef) { _refWriter.reference(value); } _writeTypeMeta(resolved, value); @@ -236,7 +225,7 @@ final class WriteContext { )) { return; } - if (!effectiveTrackRef && resolved.supportsRef) { + if (!effectiveTrackRef && resolved.needsRootRef) { _refWriter.reference(value); } _writeTypeMeta(resolved, value); @@ -410,14 +399,23 @@ final class WriteContext { } void _writeTypeMeta(TypeInfo resolved, Object value) { + final typeDef = resolved.structSerializer?.typeDefForWrite( + this, + resolved, + value, + ); + if (_typeResolver.writeInitialTypeDefMeta( + _buffer, + resolved, + typeDef: typeDef, + typeDefIds: _typeDefIds, + )) { + return; + } _typeResolver.writeTypeMeta( _buffer, resolved, - typeDef: resolved.structSerializer?.typeDefForWrite( - this, - resolved, - value, - ), + typeDef: typeDef, typeDefIds: _typeDefIds, metaStringWriter: _metaStringWriter, ); diff --git a/dart/packages/fory/lib/src/resolver/type_resolver.dart b/dart/packages/fory/lib/src/resolver/type_resolver.dart index 40be46571b..b9d095d39f 100644 --- a/dart/packages/fory/lib/src/resolver/type_resolver.dart +++ b/dart/packages/fory/lib/src/resolver/type_resolver.dart @@ -111,6 +111,8 @@ final class TypeInfo { final RegistrationKind kind; final int typeId; final bool supportsRef; + final bool needsRootRef; + final bool usesNestedTypeDefinitions; final Serializer serializer; final StructSerializer? structSerializer; final int? userTypeId; @@ -126,6 +128,8 @@ final class TypeInfo { required this.kind, required this.typeId, required this.supportsRef, + required this.needsRootRef, + required this.usesNestedTypeDefinitions, required this.serializer, required this.structSerializer, required this.userTypeId, @@ -168,6 +172,48 @@ bool usesDeclaredTypeInfo( } } +bool _fieldsNeedRootRef(List fields) { + for (final field in fields) { + if (_fieldTypeNeedsRootRef(field.fieldType)) { + return true; + } + } + return false; +} + +bool _fieldTypeNeedsRootRef(FieldType fieldType) { + if (fieldType.ref) { + return true; + } + for (final argument in fieldType.arguments) { + if (_fieldTypeNeedsRootRef(argument)) { + return true; + } + } + return false; +} + +bool _fieldsUseNestedTypeDefinitions(List fields) { + for (final field in fields) { + if (_fieldTypeUsesNestedTypeDefinitions(field.fieldType)) { + return true; + } + } + return false; +} + +bool _fieldTypeUsesNestedTypeDefinitions(FieldType fieldType) { + if (fieldType.isDynamic || TypeIds.isUserType(fieldType.typeId)) { + return true; + } + for (final argument in fieldType.arguments) { + if (_fieldTypeUsesNestedTypeDefinitions(argument)) { + return true; + } + } + return false; +} + final class TypeResolver { final Config config; final WireTypeMetaEncoder _wireTypeMetaEncoder = const WireTypeMetaEncoder(); @@ -194,6 +240,8 @@ final class TypeResolver { final Map<_EncodedMetaStringKey, EncodedMetaString> _internedEncodedMetaStrings = <_EncodedMetaStringKey, EncodedMetaString>{}; + final Map _initialTypeMetaBytes = + LinkedHashMap.identity(); TypeResolver(this.config); @@ -219,8 +267,8 @@ final class TypeResolver { }, evolving: registration.evolving, fields: registration.fields, - compatibleFactory: registration.compatibleFactory, - compatibleReadersBySlot: registration.compatibleReadersBySlot, + needsRootRef: registration.needsRootRef, + usesNestedTypeDefinitions: registration.usesNestedTypeDefinitions, id: id, namespace: namespace, typeName: typeName, @@ -250,9 +298,9 @@ final class TypeResolver { Serializer payloadSerializer, RegistrationKind registrationKind, { bool evolving = true, + bool? needsRootRef, + bool? usesNestedTypeDefinitions, List fields = const [], - GeneratedStructCompatibleFactory? compatibleFactory, - List>? compatibleReadersBySlot, int? id, String? namespace, String? typeName, @@ -281,14 +329,19 @@ final class TypeResolver { payloadSerializer, typeDef, this, - compatibleFactory: compatibleFactory, - compatibleReadersBySlot: compatibleReadersBySlot, ); final resolved = TypeInfo( type: type, kind: registrationKind, typeId: _defaultTypeIdForType(type), supportsRef: payloadSerializer.supportsRef, + needsRootRef: registrationKind == RegistrationKind.struct + ? needsRootRef ?? _fieldsNeedRootRef(normalizedFields) + : payloadSerializer.supportsRef, + usesNestedTypeDefinitions: registrationKind == RegistrationKind.struct + ? usesNestedTypeDefinitions ?? + _fieldsUseNestedTypeDefinitions(normalizedFields) + : true, serializer: payloadSerializer, structSerializer: structSerializer, userTypeId: id, @@ -541,6 +594,8 @@ final class TypeResolver { return resolved; } + TypeInfo? expectedRootType() => _registeredByType[T]; + TypeInfo resolveUserByEncodedName( EncodedMetaString namespace, EncodedMetaString typeName, @@ -584,13 +639,13 @@ final class TypeResolver { SerializationFieldInfo serializationFieldInfo( FieldInfo field, { - required int slot, + required int index, }) { final fieldType = field.fieldType; if (fieldType.isDynamic || (fieldType.isPrimitive && !fieldType.nullable)) { return SerializationFieldInfo( field: field, - slot: slot, + index: index, declaredTypeInfo: null, usesDeclaredType: false, ); @@ -598,7 +653,7 @@ final class TypeResolver { final declaredTypeInfo = tryResolveFieldType(fieldType); return SerializationFieldInfo( field: field, - slot: slot, + index: index, declaredTypeInfo: declaredTypeInfo, usesDeclaredType: declaredTypeInfo != null ? usesDeclaredTypeInfo( @@ -656,6 +711,69 @@ final class TypeResolver { } } + bool writeInitialTypeDefMeta( + Buffer buffer, + TypeInfo resolved, { + required TypeDef? typeDef, + required LinkedHashMap typeDefIds, + }) { + final resolvedTypeDef = resolved.typeDef; + if (typeDefIds.isNotEmpty || + resolvedTypeDef == null || + !identical(typeDef ?? resolvedTypeDef, resolvedTypeDef)) { + return false; + } + final wireTypeId = _wireTypeMetaEncoder.wireTypeIdFor(config, resolved); + if (!_wireTypeWritesTypeDef(wireTypeId)) { + return false; + } + final bytes = _initialTypeMetaBytes.putIfAbsent( + resolved, + () => _encodeInitialTypeDefMeta(wireTypeId, resolvedTypeDef), + ); + buffer.writeBytes(bytes); + if (resolved.usesNestedTypeDefinitions) { + typeDefIds[resolvedTypeDef] = 0; + } + return true; + } + + TypeInfo? readExpectedInitialTypeDefMeta( + Buffer buffer, + TypeInfo expected, { + required List sharedTypes, + }) { + final start = bufferReaderIndex(buffer); + final wireTypeId = buffer.readVarUint32Small7(); + if (wireTypeId != _wireTypeMetaEncoder.wireTypeIdFor(config, expected) || + !_wireTypeWritesTypeDef(wireTypeId)) { + bufferSetReaderIndex(buffer, start); + return null; + } + final marker = buffer.readVarUint32Small14(); + if (marker != 0) { + bufferSetReaderIndex(buffer, start); + return null; + } + final header = TypeHeader(buffer.readInt64()); + final expectedTypeDef = expected.typeDef; + if (expectedTypeDef == null || expectedTypeDef.header != header.value) { + bufferSetReaderIndex(buffer, start); + return null; + } + header.skipRemaining(buffer); + sharedTypes.add(expected); + return expected; + } + + Uint8List _encodeInitialTypeDefMeta(int wireTypeId, TypeDef typeDef) { + final buffer = Buffer(typeDef.encoded.length + 2); + buffer.writeVarUint32Small7(wireTypeId); + buffer.writeVarUint32(0); + buffer.writeBytes(typeDef.encoded); + return buffer.toBytes(); + } + @pragma('vm:prefer-inline') bool _wireTypeWritesUserTypeId(int wireTypeId) => wireTypeId == TypeIds.enumById || @@ -1053,6 +1171,8 @@ final class TypeResolver { kind: resolved.kind, typeId: resolved.typeId, supportsRef: resolved.supportsRef, + needsRootRef: resolved.needsRootRef, + usesNestedTypeDefinitions: resolved.usesNestedTypeDefinitions, serializer: resolved.serializer, structSerializer: resolved.structSerializer, userTypeId: resolved.userTypeId, @@ -1254,6 +1374,8 @@ final class TypeResolver { kind: RegistrationKind.builtin, typeId: typeId, supportsRef: TypeIds.supportsRef(typeId), + needsRootRef: false, + usesNestedTypeDefinitions: false, serializer: _builtinSerializerFor(typeId, type), structSerializer: null, userTypeId: null, diff --git a/dart/packages/fory/lib/src/serializer/compatible_struct_metadata.dart b/dart/packages/fory/lib/src/serializer/compatible_struct_metadata.dart deleted file mode 100644 index 2a41ee7574..0000000000 --- a/dart/packages/fory/lib/src/serializer/compatible_struct_metadata.dart +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import 'package:fory/src/meta/type_def.dart'; - -abstract final class CompatibleStructMetadata { - static final Expando _remoteTypeDefs = - Expando('fory_remote_type_def'); - - static TypeDef? remoteTypeDefFor(Object value) => _remoteTypeDefs[value]; - - static void rememberRemoteTypeDef(Object value, TypeDef typeDef) { - _remoteTypeDefs[value] = typeDef; - } -} diff --git a/dart/packages/fory/lib/src/serializer/generated_struct_serializer.dart b/dart/packages/fory/lib/src/serializer/generated_struct_serializer.dart new file mode 100644 index 0000000000..b1e4618d95 --- /dev/null +++ b/dart/packages/fory/lib/src/serializer/generated_struct_serializer.dart @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import 'package:meta/meta.dart'; + +import 'package:fory/src/context/read_context.dart'; +import 'package:fory/src/meta/field_info.dart'; +import 'package:fory/src/serializer/collection_serializers.dart'; +import 'package:fory/src/serializer/serialization_field_info.dart'; +import 'package:fory/src/serializer/serializer.dart'; +import 'package:fory/src/serializer/serializer_support.dart'; + +@internal +abstract interface class GeneratedStructSerializer implements Serializer { + T readCompatibleStruct( + ReadContext context, + CompatibleStructReadLayout layout, + ); +} + +@internal +final class CompatibleStructReadLayout { + final List remoteFields; + final List fields; + final List? _topLevelListArrayPairs; + + const CompatibleStructReadLayout( + this.remoteFields, + this.fields, [ + this._topLevelListArrayPairs, + ]); + + int get fieldCount => remoteFields.length; + + FieldInfo remoteFieldAt(int index) => remoteFields[index]; + + SerializationFieldInfo? localFieldAt(int index) => fields[index]; + + bool topLevelListArrayPairAt(int index) => + _topLevelListArrayPairs?[index] ?? false; +} + +@internal +@pragma('vm:never-inline') +Object? readGeneratedCompatibleStructField( + ReadContext context, + CompatibleStructReadLayout layout, + int index, +) { + final localField = layout.localFieldAt(index)!; + if (layout.topLevelListArrayPairAt(index)) { + return readCompatibleMatchedCollectionArrayField( + context, + localField, + layout.remoteFieldAt(index), + ); + } + return readFieldValue(context, localField); +} + +@internal +@pragma('vm:never-inline') +void skipGeneratedCompatibleStructField( + ReadContext context, + CompatibleStructReadLayout layout, + int index, +) { + readCompatibleField(context, layout.remoteFieldAt(index)); +} diff --git a/dart/packages/fory/lib/src/serializer/serialization_field_info.dart b/dart/packages/fory/lib/src/serializer/serialization_field_info.dart index 5aeff8d650..fc175df490 100644 --- a/dart/packages/fory/lib/src/serializer/serialization_field_info.dart +++ b/dart/packages/fory/lib/src/serializer/serialization_field_info.dart @@ -23,7 +23,7 @@ import 'package:fory/src/resolver/type_resolver.dart'; final class SerializationFieldInfo { final FieldInfo field; - final int slot; + final int index; TypeInfo? _declaredTypeInfo; TypeResolver? _declaredTypeInfoResolver; bool _hasDeclaredTypeInfo = false; @@ -32,7 +32,7 @@ final class SerializationFieldInfo { SerializationFieldInfo({ required this.field, - required this.slot, + required this.index, TypeInfo? declaredTypeInfo, bool? usesDeclaredType, }) : _declaredTypeInfo = declaredTypeInfo, diff --git a/dart/packages/fory/lib/src/serializer/serializer_support.dart b/dart/packages/fory/lib/src/serializer/serializer_support.dart index 50a90b7179..9d1b91fee1 100644 --- a/dart/packages/fory/lib/src/serializer/serializer_support.dart +++ b/dart/packages/fory/lib/src/serializer/serializer_support.dart @@ -29,12 +29,6 @@ import 'package:fory/src/types/float32.dart'; import 'package:fory/src/types/int64.dart'; import 'package:fory/src/types/uint64.dart'; -final class DeferredReadRef { - final int id; - - const DeferredReadRef(this.id); -} - TypeInfo? fieldDeclaredTypeInfo( TypeResolver resolver, SerializationFieldInfo field, @@ -303,42 +297,6 @@ TypeInfo? _compatibleFieldDeclaredTypeInfo( return resolver.resolveFieldType(fieldType); } -FieldInfo mergeCompatibleWriteField( - FieldInfo localField, - FieldInfo remoteField, -) { - FieldType mergeFieldType( - FieldType local, - FieldType remote, - ) { - final mergedArguments = []; - final argumentCount = remote.arguments.length; - for (var index = 0; index < argumentCount; index += 1) { - final remoteArgument = remote.arguments[index]; - final localArgument = index < local.arguments.length - ? local.arguments[index] - : remoteArgument; - mergedArguments.add(mergeFieldType(localArgument, remoteArgument)); - } - return FieldType( - type: local.type, - declaredTypeName: local.declaredTypeName, - typeId: remote.typeId, - nullable: remote.nullable, - ref: remote.ref, - dynamic: local.dynamic ?? remote.dynamic, - arguments: mergedArguments, - ); - } - - return FieldInfo( - name: localField.name, - identifier: localField.identifier, - id: localField.id, - fieldType: mergeFieldType(localField.fieldType, remoteField.fieldType), - ); -} - FieldInfo mergeCompatibleReadField( FieldInfo localField, FieldInfo remoteField, diff --git a/dart/packages/fory/lib/src/serializer/struct_serializer.dart b/dart/packages/fory/lib/src/serializer/struct_serializer.dart index f810749cbd..0f62f3416d 100644 --- a/dart/packages/fory/lib/src/serializer/struct_serializer.dart +++ b/dart/packages/fory/lib/src/serializer/struct_serializer.dart @@ -24,21 +24,12 @@ import 'package:fory/src/meta/field_type.dart'; import 'package:fory/src/meta/type_def.dart'; import 'package:fory/src/resolver/type_resolver.dart'; import 'package:fory/src/serializer/collection_serializers.dart'; -import 'package:fory/src/serializer/compatible_struct_metadata.dart'; -import 'package:fory/src/serializer/serializer.dart'; +import 'package:fory/src/serializer/generated_struct_serializer.dart'; import 'package:fory/src/serializer/serialization_field_info.dart'; +import 'package:fory/src/serializer/serializer.dart'; import 'package:fory/src/serializer/serializer_support.dart'; -import 'package:fory/src/serializer/struct_slots.dart'; import 'package:fory/src/util/hash_util.dart'; -typedef GeneratedStructCompatibleFactory = T Function(); - -typedef GeneratedStructCompatibleFieldReader = void Function( - ReadContext context, - T value, - Object? rawValue, -); - final class StructSerializer extends Serializer { final Serializer _payloadSerializer; final TypeDef _typeDef; @@ -47,9 +38,9 @@ final class StructSerializer extends Serializer { List.unmodifiable( List.generate( _typeDef.fields.length, - (slot) => _typeResolver.serializationFieldInfo( - _typeDef.fields[slot], - slot: slot, + (index) => _typeResolver.serializationFieldInfo( + _typeDef.fields[index], + index: index, ), ), ); @@ -57,20 +48,14 @@ final class StructSerializer extends Serializer { { for (final field in _localFields) field.identifier: field, }; - final Expando<_CompatibleReadLayout> _compatibleReadLayouts = - Expando<_CompatibleReadLayout>('fory_compatible_read_layout'); - final GeneratedStructCompatibleFactory? _compatibleFactory; - final List>? - _compatibleReadersBySlot; + final Expando _compatibleReadLayouts = + Expando('fory_compatible_read_layout'); StructSerializer( this._payloadSerializer, this._typeDef, - this._typeResolver, { - GeneratedStructCompatibleFactory? compatibleFactory, - List>? compatibleReadersBySlot, - }) : _compatibleFactory = compatibleFactory, - _compatibleReadersBySlot = compatibleReadersBySlot; + this._typeResolver, + ); @override bool get supportsRef => _payloadSerializer.supportsRef; @@ -100,14 +85,10 @@ final class StructSerializer extends Serializer { TypeInfo resolved, Object value, ) { - final internal = context; if (!context.config.compatible && context.config.checkStructVersion) { context.buffer.writeUint32(schemaHash(_typeDef)); } - final previousCompatibleFields = - _replaceWriteSlots(internal, resolved, value); _payloadSerializer.write(context, value); - internal.structWriteSlots = previousCompatibleFields; } @pragma('vm:prefer-inline') @@ -116,201 +97,62 @@ final class StructSerializer extends Serializer { TypeInfo resolved, { bool hasCurrentPreservedRef = false, }) { - final internal = context; - if (!context.config.compatible && context.config.checkStructVersion) { - final expected = schemaHash(_typeDef); - final actual = context.buffer.readUint32(); - if (actual != expected) { - throw StateError( - 'Struct schema version mismatch for ${resolved.type}: $actual != $expected.', - ); - } - } if (context.config.compatible && resolved.isCompatibleStruct && resolved.remoteTypeDef != null) { return _readCompatible( - internal, + context, resolved, - hasCurrentPreservedRef: hasCurrentPreservedRef, ); } - final previousReadSlots = internal.structReadSlots; - internal.structReadSlots = null; - final value = internal.readSerializerPayload( - _payloadSerializer, - resolved, - hasCurrentPreservedRef: hasCurrentPreservedRef, - ) as Object; - internal.structReadSlots = previousReadSlots; - _rememberRemoteMetadata(internal, resolved, value); - return value; + return readSameTypeValue(context, resolved); } - StructWriteSlots? _replaceWriteSlots( - WriteContext context, + @pragma('vm:prefer-inline') + Object readSameTypeValue( + ReadContext context, TypeInfo resolved, - Object value, ) { - final previous = context.structWriteSlots; - context.structWriteSlots = null; - return previous; - } - - Object _readCompatible( - ReadContext context, - TypeInfo resolved, { - required bool hasCurrentPreservedRef, - }) { - final layout = _compatibleReadLayoutForResolved(resolved); - if (layout is _CompatibleCollectionArrayReadLayout) { - return _readCompatibleCollectionArray( - context, - resolved, - layout, - hasCurrentPreservedRef: hasCurrentPreservedRef, - ); - } - final compatibleFactory = _compatibleFactory; - final compatibleReadersBySlot = _compatibleReadersBySlot; - if (compatibleFactory != null && compatibleReadersBySlot != null) { - final int? sentinelId; - final needsSentinel = resolved.supportsRef && !hasCurrentPreservedRef; - if (needsSentinel) { - sentinelId = context.refReader.preserveRefId(-1); - } else { - sentinelId = null; - } - final value = compatibleFactory(); - context.reference(value); - for (var index = 0; index < layout.fields.length; index += 1) { - final localField = layout.fields[index]; - if (localField == null) { - readCompatibleField(context, layout.remoteFields[index]); - continue; - } - compatibleReadersBySlot[localField.slot]( - context, - value, - readFieldValue(context, localField), + if (!context.config.compatible && context.config.checkStructVersion) { + final expected = schemaHash(_typeDef); + final actual = context.buffer.readUint32(); + if (actual != expected) { + throw StateError( + 'Struct schema version mismatch for ${resolved.type}: $actual != $expected.', ); } - if (needsSentinel && - context.refReader.hasPreservedRefId && - context.refReader.lastPreservedRefId == sentinelId) { - context.refReader.reference(null); - } - _rememberRemoteMetadata(context, resolved, value); - return value; - } - final compatibleValues = List.filled(_localFields.length, null); - final presentSlots = List.filled(_localFields.length, false); - for (var index = 0; index < layout.fields.length; index += 1) { - final localField = layout.fields[index]; - if (localField == null) { - readCompatibleField(context, layout.remoteFields[index]); - continue; - } - final slot = localField.slot; - compatibleValues[slot] = readFieldValue( - context, - localField, - ); - presentSlots[slot] = true; } - final previousCompatibleFields = context.structReadSlots; - context.structReadSlots = StructReadSlots(compatibleValues, presentSlots); - final value = context.readSerializerPayload( - _payloadSerializer, - resolved, - hasCurrentPreservedRef: hasCurrentPreservedRef, - ) as Object; - context.structReadSlots = previousCompatibleFields; - _rememberRemoteMetadata(context, resolved, value); - return value; + return _payloadSerializer.read(context) as Object; } - Object _readCompatibleCollectionArray( + @pragma('vm:never-inline') + Object _readCompatible( ReadContext context, TypeInfo resolved, - _CompatibleCollectionArrayReadLayout layout, { - required bool hasCurrentPreservedRef, - }) { - final compatibleFactory = _compatibleFactory; - final compatibleReadersBySlot = _compatibleReadersBySlot; - if (compatibleFactory != null && compatibleReadersBySlot != null) { - final int? sentinelId; - final needsSentinel = resolved.supportsRef && !hasCurrentPreservedRef; - if (needsSentinel) { - sentinelId = context.refReader.preserveRefId(-1); - } else { - sentinelId = null; - } - final value = compatibleFactory(); - context.reference(value); - for (var index = 0; index < layout.fields.length; index += 1) { - final localField = layout.fields[index]; - if (localField == null) { - readCompatibleField(context, layout.remoteFields[index]); - continue; - } - compatibleReadersBySlot[localField.slot]( - context, - value, - layout.topLevelListArrayPairs[index] - ? readCompatibleMatchedCollectionArrayField( - context, - localField, - layout.remoteFields[index], - ) - : readFieldValue(context, localField), - ); - } - if (needsSentinel && - context.refReader.hasPreservedRefId && - context.refReader.lastPreservedRefId == sentinelId) { - context.refReader.reference(null); - } - _rememberRemoteMetadata(context, resolved, value); - return value; + ) { + final payloadSerializer = _payloadSerializer; + if (payloadSerializer is! GeneratedStructSerializer) { + throw StateError( + 'Compatible struct read for ${resolved.type} requires a generated struct serializer.', + ); } - final compatibleValues = List.filled(_localFields.length, null); - final presentSlots = List.filled(_localFields.length, false); - for (var index = 0; index < layout.fields.length; index += 1) { - final localField = layout.fields[index]; - if (localField == null) { - readCompatibleField(context, layout.remoteFields[index]); - continue; - } - final slot = localField.slot; - compatibleValues[slot] = layout.topLevelListArrayPairs[index] - ? readCompatibleMatchedCollectionArrayField( - context, - localField, - layout.remoteFields[index], - ) - : readFieldValue(context, localField); - presentSlots[slot] = true; + final layout = _compatibleReadLayoutForResolved(resolved); + final value = payloadSerializer.readCompatibleStruct(context, layout); + if (value == null) { + throw StateError( + 'Compatible struct read for ${resolved.type} returned null.', + ); } - final previousCompatibleFields = context.structReadSlots; - context.structReadSlots = StructReadSlots(compatibleValues, presentSlots); - final value = context.readSerializerPayload( - _payloadSerializer, - resolved, - hasCurrentPreservedRef: hasCurrentPreservedRef, - ) as Object; - context.structReadSlots = previousCompatibleFields; - _rememberRemoteMetadata(context, resolved, value); return value; } @pragma('vm:prefer-inline') - _CompatibleReadLayout _compatibleReadLayoutForResolved( + CompatibleStructReadLayout _compatibleReadLayoutForResolved( TypeInfo resolved, ) { final remoteTypeDef = resolved.remoteTypeDef; if (remoteTypeDef == null) { - return _CompatibleReadLayout( + return CompatibleStructReadLayout( _typeDef.fields, _localFields, ); @@ -322,7 +164,7 @@ final class StructSerializer extends Serializer { return _buildCompatibleReadLayout(remoteTypeDef); } - _CompatibleReadLayout _buildCompatibleReadLayout(TypeDef remoteTypeDef) { + CompatibleStructReadLayout _buildCompatibleReadLayout(TypeDef remoteTypeDef) { final fields = []; List? topLevelListArrayPairs; var hasTopLevelListArrayPairs = false; @@ -353,36 +195,21 @@ final class StructSerializer extends Serializer { ? localField : _typeResolver.serializationFieldInfo( mergeCompatibleReadField(localField.field, remoteField), - slot: localField.slot, + index: localField.index, ); fields.add(mergedField); topLevelListArrayPairs?.add(topLevelListArrayPair); } - final frozenFields = List.unmodifiable(fields); - final layout = hasTopLevelListArrayPairs - ? _CompatibleCollectionArrayReadLayout( - remoteTypeDef.fields, - frozenFields, - List.unmodifiable(topLevelListArrayPairs!), - ) - : _CompatibleReadLayout( - remoteTypeDef.fields, - frozenFields, - ); + final layout = CompatibleStructReadLayout( + remoteTypeDef.fields, + List.unmodifiable(fields), + hasTopLevelListArrayPairs + ? List.unmodifiable(topLevelListArrayPairs!) + : null, + ); _compatibleReadLayouts[remoteTypeDef] = layout; return layout; } - - void _rememberRemoteMetadata( - ReadContext context, - TypeInfo resolved, - Object value, - ) { - final remoteTypeDef = resolved.remoteTypeDef; - if (remoteTypeDef != null) { - CompatibleStructMetadata.rememberRemoteTypeDef(value, remoteTypeDef); - } - } } bool _topLevelListArrayPair(FieldInfo localField, FieldInfo remoteField) { @@ -413,20 +240,3 @@ bool _hasUnsupportedListArrayMismatch( } return false; } - -final class _CompatibleReadLayout { - final List remoteFields; - final List fields; - - const _CompatibleReadLayout(this.remoteFields, this.fields); -} - -final class _CompatibleCollectionArrayReadLayout extends _CompatibleReadLayout { - final List topLevelListArrayPairs; - - const _CompatibleCollectionArrayReadLayout( - super.remoteFields, - super.fields, - this.topLevelListArrayPairs, - ); -} diff --git a/dart/packages/fory/lib/src/serializer/struct_slots.dart b/dart/packages/fory/lib/src/serializer/struct_slots.dart deleted file mode 100644 index f3848798cf..0000000000 --- a/dart/packages/fory/lib/src/serializer/struct_slots.dart +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import 'package:meta/meta.dart'; -import 'package:fory/src/serializer/serialization_field_info.dart'; - -@internal -final class StructWriteSlots { - final List orderedFields; - final List _fieldsBySlot; - - StructWriteSlots(this.orderedFields, int fieldCount) - : _fieldsBySlot = List.filled(fieldCount, null) { - for (final field in orderedFields) { - _fieldsBySlot[field.slot] = field; - } - } - - SerializationFieldInfo? fieldForSlot(int slot) => _fieldsBySlot[slot]; -} - -@internal -final class StructReadSlots { - final List compatibleValues; - final List presentSlots; - - const StructReadSlots(this.compatibleValues, this.presentSlots); - - bool containsSlot(int slot) => presentSlots[slot]; - - Object? valueForSlot(int slot) => compatibleValues[slot]; -} diff --git a/dart/packages/fory/test/buffer_test.dart b/dart/packages/fory/test/buffer_test.dart index 2a5ddb1f70..2da7443c1e 100644 --- a/dart/packages/fory/test/buffer_test.dart +++ b/dart/packages/fory/test/buffer_test.dart @@ -174,7 +174,6 @@ void main() { expectedBytes: testCase.bytes, write: (buffer, value) => buffer.writeVarUint32(value), read: (buffer) => buffer.readVarUint32(), - cursorRead: (cursor) => cursor.readVarUint32(), ); } }); @@ -206,7 +205,6 @@ void main() { expectedBytes: testCase.bytes, write: (buffer, value) => buffer.writeVarInt32(value), read: (buffer) => buffer.readVarInt32(), - cursorRead: (cursor) => cursor.readVarInt32(), ); } }); @@ -243,7 +241,6 @@ void main() { expectedBytes: testCase.bytes, write: (buffer, value) => buffer.writeVarUint64(value), read: (buffer) => buffer.readVarUint64(), - cursorRead: (cursor) => cursor.readVarUint64(), ); } }); @@ -280,7 +277,6 @@ void main() { expectedBytes: testCase.bytes, write: (buffer, value) => buffer.writeVarInt64(value), read: (buffer) => buffer.readVarInt64(), - cursorRead: (cursor) => cursor.readVarInt64(), ); } }); @@ -306,7 +302,6 @@ void main() { expectedBytes: testCase.bytes, write: (buffer, value) => buffer.writeTaggedInt64(value), read: (buffer) => buffer.readTaggedInt64(), - cursorRead: (cursor) => cursor.readTaggedInt64(), ); } }); @@ -332,7 +327,6 @@ void main() { expectedBytes: testCase.bytes, write: (buffer, value) => buffer.writeTaggedUint64(value), read: (buffer) => buffer.readTaggedUint64(), - cursorRead: (cursor) => cursor.readTaggedUint64(), ); } }, @@ -358,20 +352,12 @@ void main() { writeInt: (buffer, value) => buffer.writeInt64FromInt(value), writeWrapper: (buffer, value) => buffer.writeInt64(Int64(value)), readInt: (buffer) => buffer.readInt64AsInt(), - cursorWriteInt: (cursor, value) => cursor.writeInt64FromInt(value), - cursorWriteWrapper: (cursor, value) => - cursor.writeInt64(Int64(value)), - cursorReadInt: (cursor) => cursor.readInt64AsInt(), ); _expectInt64IntHelperMatchesWrapper( value: value, writeInt: (buffer, value) => buffer.writeVarInt64FromInt(value), writeWrapper: (buffer, value) => buffer.writeVarInt64(Int64(value)), readInt: (buffer) => buffer.readVarInt64AsInt(), - cursorWriteInt: (cursor, value) => cursor.writeVarInt64FromInt(value), - cursorWriteWrapper: (cursor, value) => - cursor.writeVarInt64(Int64(value)), - cursorReadInt: (cursor) => cursor.readVarInt64AsInt(), ); _expectInt64IntHelperMatchesWrapper( value: value, @@ -379,45 +365,6 @@ void main() { writeWrapper: (buffer, value) => buffer.writeTaggedInt64(Int64(value)), readInt: (buffer) => buffer.readTaggedInt64AsInt(), - cursorWriteInt: (cursor, value) => - cursor.writeTaggedInt64FromInt(value), - cursorWriteWrapper: (cursor, value) => - cursor.writeTaggedInt64(Int64(value)), - cursorReadInt: (cursor) => cursor.readTaggedInt64AsInt(), - ); - } - }); - - test( - 'uint64 cursor int helpers match Uint64 wrapper encodings at safe boundaries', - () { - const cases = [ - 0, - 1, - 0x7fffffff, - 0x80000000, - 0xffffffff, - _jsSafeIntMax, - ]; - - for (final value in cases) { - _expectUint64CursorIntHelperMatchesWrapper( - value: value, - cursorWriteInt: (cursor, value) => cursor.writeUint64FromInt(value), - cursorWriteWrapper: (cursor, value) => cursor.writeUint64(value), - ); - _expectUint64CursorIntHelperMatchesWrapper( - value: value, - cursorWriteInt: (cursor, value) => - cursor.writeVarUint64FromInt(value), - cursorWriteWrapper: (cursor, value) => cursor.writeVarUint64(value), - ); - _expectUint64CursorIntHelperMatchesWrapper( - value: value, - cursorWriteInt: (cursor, value) => - cursor.writeTaggedUint64FromInt(value), - cursorWriteWrapper: (cursor, value) => - cursor.writeTaggedUint64(value), ); } }); @@ -466,53 +413,6 @@ void main() { ); }); - test('web rejects unsafe uint64 cursor int helper values', () { - if (!identical(1, 1.0)) { - for (final value in [-1, _jsUnsafeInt]) { - _expectUint64CursorIntHelperMatchesWrapper( - value: value, - cursorWriteInt: (cursor, value) => cursor.writeUint64FromInt(value), - cursorWriteWrapper: (cursor, value) => cursor.writeUint64(value), - ); - _expectUint64CursorIntHelperMatchesWrapper( - value: value, - cursorWriteInt: (cursor, value) => - cursor.writeVarUint64FromInt(value), - cursorWriteWrapper: (cursor, value) => cursor.writeVarUint64(value), - ); - _expectUint64CursorIntHelperMatchesWrapper( - value: value, - cursorWriteInt: (cursor, value) => - cursor.writeTaggedUint64FromInt(value), - cursorWriteWrapper: (cursor, value) => - cursor.writeTaggedUint64(value), - ); - } - return; - } - - for (final value in [-1, _jsUnsafeInt]) { - expect( - () => _writeWithCursor((cursor) => cursor.writeUint64FromInt(value)), - throwsA(isA()), - reason: 'fixed $value', - ); - expect( - () => - _writeWithCursor((cursor) => cursor.writeVarUint64FromInt(value)), - throwsA(isA()), - reason: 'varint $value', - ); - expect( - () => _writeWithCursor( - (cursor) => cursor.writeTaggedUint64FromInt(value), - ), - throwsA(isA()), - reason: 'tagged $value', - ); - } - }); - test('round-trips small varuint helpers', () { const small7Cases = <({int bytes, int value})>[ (bytes: 1, value: 0), @@ -560,81 +460,6 @@ void main() { ); } }); - - test('generated cursors match Buffer encodings', () { - final buffer = Buffer(); - final generated = Buffer(); - - buffer.writeBool(true); - buffer.writeByte(-7); - buffer.writeUint8(250); - buffer.writeInt16(-1234); - buffer.writeUint16(65000); - buffer.writeInt32(-123456789); - buffer.writeUint32(0x89abcdef); - buffer.writeInt64(_i64Hex('-1234567890abcdef')); - buffer.writeUint64(_u64Hex('fedcba9876543210')); - buffer.writeFloat16(Float16(1.5)); - buffer.writeBfloat16(Bfloat16(2.5)); - buffer.writeFloat32(3.25); - buffer.writeFloat64(-9.5); - buffer.writeVarUint32(0xffffffff); - buffer.writeVarInt32(-0x40000000); - buffer.writeVarUint64(_u64Hex('ffffffffffffffff')); - buffer.writeVarInt64(_i64Hex('c000000000000000')); - buffer.writeTaggedInt64(Int64(0x40000000)); - buffer.writeTaggedUint64(Uint64(0x80000000)); - - final cursor = GeneratedWriteCursor.reserve(generated, 128); - cursor.writeBool(true); - cursor.writeByte(-7); - cursor.writeUint8(250); - cursor.writeInt16(-1234); - cursor.writeUint16(65000); - cursor.writeInt32(-123456789); - cursor.writeUint32(0x89abcdef); - cursor.writeInt64(_i64Hex('-1234567890abcdef')); - cursor.writeUint64(_u64Hex('fedcba9876543210')); - cursor.writeFloat16(Float16(1.5)); - cursor.writeBfloat16(Bfloat16(2.5)); - cursor.writeFloat32(3.25); - cursor.writeFloat64(-9.5); - cursor.writeVarUint32(0xffffffff); - cursor.writeVarInt32(-0x40000000); - cursor.writeVarUint64(_u64Hex('ffffffffffffffff')); - cursor.writeVarInt64(_i64Hex('c000000000000000')); - cursor.writeTaggedInt64(Int64(0x40000000)); - cursor.writeTaggedUint64(Uint64(0x80000000)); - cursor.finish(); - - expect(generated.toBytes(), orderedEquals(buffer.toBytes())); - - final readBuffer = Buffer.wrap(Uint8List.fromList(generated.toBytes())); - final readCursor = GeneratedReadCursor.start(readBuffer); - - expect(readCursor.readBool(), isTrue); - expect(readCursor.readByte(), equals(-7)); - expect(readCursor.readUint8(), equals(250)); - expect(readCursor.readInt16(), equals(-1234)); - expect(readCursor.readUint16(), equals(65000)); - expect(readCursor.readInt32(), equals(-123456789)); - expect(readCursor.readUint32(), equals(0x89abcdef)); - expect(readCursor.readInt64(), equals(_i64Hex('-1234567890abcdef'))); - expect(readCursor.readUint64(), equals(_u64Hex('fedcba9876543210'))); - expect(readCursor.readFloat16(), equals(Float16(1.5))); - expect(readCursor.readBfloat16(), equals(Bfloat16(2.5))); - expect(readCursor.readFloat32(), closeTo(3.25, 0.0001)); - expect(readCursor.readFloat64(), equals(-9.5)); - expect(readCursor.readVarUint32(), equals(0xffffffff)); - expect(readCursor.readVarInt32(), equals(-0x40000000)); - expect(readCursor.readVarUint64(), equals(_u64Hex('ffffffffffffffff'))); - expect(readCursor.readVarInt64(), equals(_i64Hex('c000000000000000'))); - expect(readCursor.readTaggedInt64(), equals(Int64(0x40000000))); - expect(readCursor.readTaggedUint64(), equals(Uint64(0x80000000))); - - readCursor.finish(); - expect(readBuffer.readableBytes, equals(0)); - }); }); } @@ -643,10 +468,6 @@ void _expectInt64IntHelperMatchesWrapper({ required void Function(Buffer buffer, int value) writeInt, required void Function(Buffer buffer, int value) writeWrapper, required int Function(Buffer buffer) readInt, - required void Function(GeneratedWriteCursor cursor, int value) cursorWriteInt, - required void Function(GeneratedWriteCursor cursor, int value) - cursorWriteWrapper, - required int Function(GeneratedReadCursor cursor) cursorReadInt, }) { final intBuffer = Buffer(); writeInt(intBuffer, value); @@ -658,50 +479,6 @@ void _expectInt64IntHelperMatchesWrapper({ final readBuffer = Buffer.wrap(Uint8List.fromList(intBuffer.toBytes())); expect(readInt(readBuffer), equals(value)); expect(readBuffer.readableBytes, equals(0)); - - final cursorIntBuffer = Buffer(); - final cursorInt = GeneratedWriteCursor.reserve(cursorIntBuffer, 10); - cursorWriteInt(cursorInt, value); - cursorInt.finish(); - expect(cursorIntBuffer.toBytes(), orderedEquals(wrapperBuffer.toBytes())); - - final cursorWrapperBuffer = Buffer(); - final cursorWrapper = GeneratedWriteCursor.reserve(cursorWrapperBuffer, 10); - cursorWriteWrapper(cursorWrapper, value); - cursorWrapper.finish(); - expect(cursorWrapperBuffer.toBytes(), orderedEquals(wrapperBuffer.toBytes())); - - final cursorReadBuffer = Buffer.wrap(Uint8List.fromList(intBuffer.toBytes())); - final cursorRead = GeneratedReadCursor.start(cursorReadBuffer); - expect(cursorReadInt(cursorRead), equals(value)); - cursorRead.finish(); - expect(cursorReadBuffer.readableBytes, equals(0)); -} - -void _expectUint64CursorIntHelperMatchesWrapper({ - required int value, - required void Function(GeneratedWriteCursor cursor, int value) cursorWriteInt, - required void Function(GeneratedWriteCursor cursor, Uint64 value) - cursorWriteWrapper, -}) { - final intBuffer = Buffer(); - final intCursor = GeneratedWriteCursor.reserve(intBuffer, 10); - cursorWriteInt(intCursor, value); - intCursor.finish(); - - final wrapperBuffer = Buffer(); - final wrapperCursor = GeneratedWriteCursor.reserve(wrapperBuffer, 10); - cursorWriteWrapper(wrapperCursor, Uint64(value)); - wrapperCursor.finish(); - - expect(intBuffer.toBytes(), orderedEquals(wrapperBuffer.toBytes())); -} - -void _writeWithCursor(void Function(GeneratedWriteCursor cursor) write) { - final buffer = Buffer(); - final cursor = GeneratedWriteCursor.reserve(buffer, 10); - write(cursor); - cursor.finish(); } void _expectEncodedIntRoundTrip({ @@ -709,7 +486,6 @@ void _expectEncodedIntRoundTrip({ required int expectedBytes, required void Function(Buffer buffer, int value) write, required int Function(Buffer buffer) read, - int Function(GeneratedReadCursor cursor)? cursorRead, }) { final buffer = Buffer(); write(buffer, value); @@ -720,14 +496,6 @@ void _expectEncodedIntRoundTrip({ final wrapped = Buffer.wrap(Uint8List.fromList(bytes)); expect(read(wrapped), equals(value)); expect(wrapped.readableBytes, equals(0)); - - if (cursorRead != null) { - final cursorBuffer = Buffer.wrap(Uint8List.fromList(bytes)); - final cursor = GeneratedReadCursor.start(cursorBuffer); - expect(cursorRead(cursor), equals(value)); - cursor.finish(); - expect(cursorBuffer.readableBytes, equals(0)); - } } void _expectEncodedUint64RoundTrip({ @@ -735,7 +503,6 @@ void _expectEncodedUint64RoundTrip({ required int expectedBytes, required void Function(Buffer buffer, Uint64 value) write, required Uint64 Function(Buffer buffer) read, - Uint64 Function(GeneratedReadCursor cursor)? cursorRead, }) { final buffer = Buffer(); write(buffer, value); @@ -746,14 +513,6 @@ void _expectEncodedUint64RoundTrip({ final wrapped = Buffer.wrap(Uint8List.fromList(bytes)); expect(read(wrapped), equals(value)); expect(wrapped.readableBytes, equals(0)); - - if (cursorRead != null) { - final cursorBuffer = Buffer.wrap(Uint8List.fromList(bytes)); - final cursor = GeneratedReadCursor.start(cursorBuffer); - expect(cursorRead(cursor), equals(value)); - cursor.finish(); - expect(cursorBuffer.readableBytes, equals(0)); - } } void _expectEncodedInt64RoundTrip({ @@ -761,7 +520,6 @@ void _expectEncodedInt64RoundTrip({ required int expectedBytes, required void Function(Buffer buffer, Int64 value) write, required Int64 Function(Buffer buffer) read, - Int64 Function(GeneratedReadCursor cursor)? cursorRead, }) { final buffer = Buffer(); write(buffer, value); @@ -772,12 +530,4 @@ void _expectEncodedInt64RoundTrip({ final wrapped = Buffer.wrap(Uint8List.fromList(bytes)); expect(read(wrapped), equals(value)); expect(wrapped.readableBytes, equals(0)); - - if (cursorRead != null) { - final cursorBuffer = Buffer.wrap(Uint8List.fromList(bytes)); - final cursor = GeneratedReadCursor.start(cursorBuffer); - expect(cursorRead(cursor), equals(value)); - cursor.finish(); - expect(cursorBuffer.readableBytes, equals(0)); - } } diff --git a/dart/packages/fory/test/nested_type_spec_test.dart b/dart/packages/fory/test/nested_type_spec_test.dart index 014bc511d5..b39387adae 100644 --- a/dart/packages/fory/test/nested_type_spec_test.dart +++ b/dart/packages/fory/test/nested_type_spec_test.dart @@ -18,7 +18,6 @@ */ import 'package:fory/fory.dart'; -import 'package:fory/src/serializer/compatible_struct_metadata.dart'; import 'package:test/test.dart'; part 'nested_type_spec_test.fory.dart'; @@ -149,7 +148,7 @@ void _registerNullableReader(Fory fory) { void main() { group('nested type specs', () { test( - 'compatible mode reads nested overridden fixed int32 list values from remote meta', + 'compatible mode reads nested overridden fixed int32 list values', () { final writer = Fory(compatible: true); final reader = Fory(compatible: true); @@ -166,20 +165,6 @@ void main() { ); expect(result.nested['a'], orderedEquals([1, null, -7])); - - final remoteTypeDef = CompatibleStructMetadata.remoteTypeDefFor(result); - expect(remoteTypeDef, isNotNull); - final nestedField = remoteTypeDef!.fields.single; - expect(nestedField.fieldType.typeId, equals(TypeIds.map)); - expect( - nestedField.fieldType.arguments[0].typeId, - equals(TypeIds.string), - ); - final remoteList = nestedField.fieldType.arguments[1]; - expect(remoteList.typeId, equals(TypeIds.list)); - final remoteElement = remoteList.arguments.single; - expect(remoteElement.typeId, equals(TypeIds.int32)); - expect(remoteElement.nullable, isTrue); }, ); @@ -209,7 +194,7 @@ void main() { }); test( - 'nested ref metadata stays out of schema hash but survives remote meta', + 'nested ref metadata stays out of schema hash and preserves refs', () { final writer = Fory(compatible: true); final reader = Fory(compatible: true); @@ -227,12 +212,6 @@ void main() { expect(result.items, hasLength(2)); expect(result.items[0].name, equals('shared')); expect(identical(result.items[0], result.items[1]), isTrue); - - final remoteTypeDef = CompatibleStructMetadata.remoteTypeDefFor(result); - expect(remoteTypeDef, isNotNull); - final remoteElement = - remoteTypeDef!.fields.single.fieldType.arguments.single; - expect(remoteElement.ref, isTrue); }, ); diff --git a/dart/packages/fory/test/object_and_compatible_serializer_test.dart b/dart/packages/fory/test/object_and_compatible_serializer_test.dart index 73174d35ac..ef1b2737a9 100644 --- a/dart/packages/fory/test/object_and_compatible_serializer_test.dart +++ b/dart/packages/fory/test/object_and_compatible_serializer_test.dart @@ -18,7 +18,6 @@ */ import 'package:fory/fory.dart'; -import 'package:fory/src/serializer/compatible_struct_metadata.dart'; import 'package:test/test.dart'; part 'object_and_compatible_serializer_test.fory.dart'; @@ -245,13 +244,6 @@ void main() { expect((migrated.payload as SharedLeaf).label, equals('shared')); expect(identical(migrated.original, migrated.duplicate), isTrue); - final remoteTypeDef = CompatibleStructMetadata.remoteTypeDefFor(migrated); - expect(remoteTypeDef, isNotNull); - expect( - remoteTypeDef!.fields.map((field) => field.identifier), - containsAll(['1', '2', '3', '4', '5']), - ); - final roundTripBack = writer.deserialize( reader.serialize(migrated), ); diff --git a/dart/packages/fory/test/signed_serializer_test.dart b/dart/packages/fory/test/signed_serializer_test.dart index 4be24b1e7b..312e2d9568 100644 --- a/dart/packages/fory/test/signed_serializer_test.dart +++ b/dart/packages/fory/test/signed_serializer_test.dart @@ -18,7 +18,6 @@ */ import 'package:fory/fory.dart'; -import 'package:fory/src/serializer/compatible_struct_metadata.dart'; import 'package:test/test.dart'; part 'signed_serializer_test.fory.dart'; @@ -237,24 +236,6 @@ void _expectSignedFieldsEqual(SignedFields actual, SignedFields expected) { expect(actual.optionalI64Tagged, equals(expected.optionalI64Tagged)); } -int _remoteFieldTypeId(Object value, String identifier) { - final remoteTypeDef = CompatibleStructMetadata.remoteTypeDefFor(value); - expect(remoteTypeDef, isNotNull); - final field = remoteTypeDef!.fields.firstWhere( - (field) => field.identifier == identifier, - ); - return field.fieldType.typeId; -} - -bool _remoteFieldNullable(Object value, String identifier) { - final remoteTypeDef = CompatibleStructMetadata.remoteTypeDefFor(value); - expect(remoteTypeDef, isNotNull); - final field = remoteTypeDef!.fields.firstWhere( - (field) => field.identifier == identifier, - ); - return field.fieldType.nullable; -} - void main() { group('signed generated fields', () { test('round trips int and Int64 encoding edge cases', () { @@ -303,19 +284,9 @@ void main() { final roundTrip = fory.deserialize(fory.serialize(value)); expect(roundTrip.i64VarInt, equals(signedMin)); expect(roundTrip.optionalI64VarInt, equals(signedMin)); - - final buffer = Buffer(); - final writeCursor = GeneratedWriteCursor.reserve(buffer, 10); - writeCursor.writeVarInt64FromInt(signedMin); - writeCursor.finish(); - - final readCursor = - GeneratedReadCursor.start(Buffer.wrap(buffer.toBytes())); - expect(readCursor.readVarInt64AsInt(), equals(signedMin)); - readCursor.finish(); }); - test('compatible metadata records signed wire types and nullability', () { + test('compatible mode reads signed fields through remote wire types', () { final writer = Fory(compatible: true); final reader = Fory(compatible: true); _registerSignedFields(writer); @@ -325,51 +296,6 @@ void main() { writer.serialize(_fullRangeWrapperSignedFields()), ); expect(roundTrip.plainInt, equals(0)); - - expect( - _remoteFieldTypeId(roundTrip, 'plain_int'), - equals(TypeIds.varInt64), - ); - expect( - _remoteFieldTypeId(roundTrip, 'i32_var'), - equals(TypeIds.varInt32), - ); - expect( - _remoteFieldTypeId(roundTrip, 'i32_fixed'), - equals(TypeIds.int32), - ); - expect( - _remoteFieldTypeId(roundTrip, 'i64_var_int'), - equals(TypeIds.varInt64), - ); - expect( - _remoteFieldTypeId(roundTrip, 'i64_fixed_int'), - equals(TypeIds.int64), - ); - expect( - _remoteFieldTypeId(roundTrip, 'i64_tagged_int'), - equals(TypeIds.taggedInt64), - ); - expect( - _remoteFieldTypeId(roundTrip, 'i64_default'), - equals(TypeIds.varInt64), - ); - expect( - _remoteFieldTypeId(roundTrip, 'i64_var'), - equals(TypeIds.varInt64), - ); - expect(_remoteFieldTypeId(roundTrip, 'i64_fixed'), equals(TypeIds.int64)); - expect( - _remoteFieldTypeId(roundTrip, 'i64_tagged'), - equals(TypeIds.taggedInt64), - ); - - expect(_remoteFieldNullable(roundTrip, 'plain_int'), isFalse); - expect(_remoteFieldNullable(roundTrip, 'optional_i32_var'), isTrue); - expect(_remoteFieldNullable(roundTrip, 'optional_i32_fixed'), isTrue); - expect(_remoteFieldNullable(roundTrip, 'optional_i64_var_int'), isTrue); - expect(_remoteFieldNullable(roundTrip, 'optional_i64_fixed'), isTrue); - expect(_remoteFieldNullable(roundTrip, 'optional_i64_tagged'), isTrue); }); test('rejects out-of-range annotated int32 fields', () { diff --git a/dart/packages/fory/test/unsigned_serializer_test.dart b/dart/packages/fory/test/unsigned_serializer_test.dart index bb1f9f23be..b45749f0c3 100644 --- a/dart/packages/fory/test/unsigned_serializer_test.dart +++ b/dart/packages/fory/test/unsigned_serializer_test.dart @@ -18,7 +18,6 @@ */ import 'package:fory/fory.dart'; -import 'package:fory/src/serializer/compatible_struct_metadata.dart'; import 'package:test/test.dart'; part 'unsigned_serializer_test.fory.dart'; @@ -353,24 +352,6 @@ void _expectUnsignedFieldsEqual( expect(actual.u64VarIntNullable, equals(expected.u64VarIntNullable)); } -int _remoteFieldTypeId(Object value, String identifier) { - final remoteTypeDef = CompatibleStructMetadata.remoteTypeDefFor(value); - expect(remoteTypeDef, isNotNull); - final field = remoteTypeDef!.fields.firstWhere( - (field) => field.identifier == identifier, - ); - return field.fieldType.typeId; -} - -bool _remoteFieldNullable(Object value, String identifier) { - final remoteTypeDef = CompatibleStructMetadata.remoteTypeDefFor(value); - expect(remoteTypeDef, isNotNull); - final field = remoteTypeDef!.fields.firstWhere( - (field) => field.identifier == identifier, - ); - return field.fieldType.nullable; -} - void main() { group('unsigned generated fields', () { test('round trips small, threshold, midpoint, max, and null cases', () { @@ -407,7 +388,7 @@ void main() { } }); - test('compatible metadata records unsigned wire types and nullability', () { + test('compatible mode reads unsigned fields through remote wire types', () { final writer = Fory(compatible: true); final reader = Fory(compatible: true); _registerUnsignedFields(writer); @@ -418,46 +399,6 @@ void main() { ); expect(roundTrip.u8, equals(0xff)); expect(roundTrip.extra, equals(42)); - - expect(_remoteFieldTypeId(roundTrip, 'u8'), equals(TypeIds.uint8)); - expect(_remoteFieldTypeId(roundTrip, 'u16'), equals(TypeIds.uint16)); - expect( - _remoteFieldTypeId(roundTrip, 'u32_var'), - equals(TypeIds.varUint32), - ); - expect( - _remoteFieldTypeId(roundTrip, 'u32_fixed'), equals(TypeIds.uint32)); - expect( - _remoteFieldTypeId(roundTrip, 'u64_var'), equals(TypeIds.varUint64)); - expect( - _remoteFieldTypeId(roundTrip, 'u64_fixed'), equals(TypeIds.uint64)); - expect( - _remoteFieldTypeId(roundTrip, 'u64_tagged'), - equals(TypeIds.taggedUint64), - ); - expect( - _remoteFieldTypeId(roundTrip, 'u64_var_int'), - equals(TypeIds.varUint64), - ); - expect( - _remoteFieldTypeId(roundTrip, 'u64_fixed_int'), - equals(TypeIds.uint64), - ); - expect( - _remoteFieldTypeId(roundTrip, 'u64_tagged_int'), - equals(TypeIds.taggedUint64), - ); - - expect(_remoteFieldNullable(roundTrip, 'u8'), isFalse); - expect(_remoteFieldNullable(roundTrip, 'u16'), isFalse); - expect(_remoteFieldNullable(roundTrip, 'u8_nullable'), isTrue); - expect(_remoteFieldNullable(roundTrip, 'u16_nullable'), isTrue); - expect(_remoteFieldNullable(roundTrip, 'u32_var_nullable'), isTrue); - expect(_remoteFieldNullable(roundTrip, 'u32_fixed_nullable'), isTrue); - expect(_remoteFieldNullable(roundTrip, 'u64_var_nullable'), isTrue); - expect(_remoteFieldNullable(roundTrip, 'u64_fixed_nullable'), isTrue); - expect(_remoteFieldNullable(roundTrip, 'u64_tagged_nullable'), isTrue); - expect(_remoteFieldNullable(roundTrip, 'u64_var_int_nullable'), isTrue); }); test('rejects out-of-range annotated unsigned fields', () { diff --git a/dart/packages/fory/test/xlang_protocol_test.dart b/dart/packages/fory/test/xlang_protocol_test.dart index 0f182af5e2..c8bb77f1a0 100644 --- a/dart/packages/fory/test/xlang_protocol_test.dart +++ b/dart/packages/fory/test/xlang_protocol_test.dart @@ -126,6 +126,8 @@ void main() { kind: RegistrationKind.builtin, typeId: TypeIds.struct, supportsRef: false, + needsRootRef: false, + usesNestedTypeDefinitions: false, serializer: _CacheTestSerializer(), structSerializer: null, userTypeId: null, diff --git a/docs/benchmarks/dart/README.md b/docs/benchmarks/dart/README.md index cc8ae23ef8..f1bbf98a22 100644 --- a/docs/benchmarks/dart/README.md +++ b/docs/benchmarks/dart/README.md @@ -4,47 +4,47 @@ This benchmark compares serialization and deserialization throughput for Apache ## Hardware and Runtime Info -| Key | Value | -| --------------------- | ---------------------------------------------------------------- | -| Timestamp | 2026-04-23T12:21:28Z | -| OS | Version 26.2 (Build 25C56) | -| Host | Macbook-Air.local | -| CPU Cores (Logical) | 8 | -| Memory (GB) | 8.00 | -| Dart | 3.10.4 (stable) (Tue Dec 9 00:01:55 2025 -0800) on "macos_arm64" | -| Samples per case | 5 | -| Warmup per case (s) | 1.0 | -| Duration per case (s) | 1.5 | +| Key | Value | +| --------------------- | ----------------------------------------------------------------- | +| Timestamp | 2026-05-07T09:12:52.301869Z | +| OS | Version 15.7.2 (Build 24G325) | +| Host | MacBook-Pro.local | +| CPU Cores (Logical) | 12 | +| Memory (GB) | 48.00 | +| Dart | 3.10.7 (stable) (Tue Dec 23 00:01:57 2025 -0800) on "macos_arm64" | +| Samples per case | 5 | +| Warmup per case (s) | 1.0 | +| Duration per case (s) | 1.5 | ## Throughput Results ![Throughput](throughput.png) -| Datatype | Operation | Fory TPS | Protobuf TPS | Fastest | -| ---------------- | ----------- | --------: | -----------: | ------------ | -| Struct | Serialize | 4,696,615 | 1,998,625 | fory (2.35x) | -| Struct | Deserialize | 5,815,245 | 4,173,568 | fory (1.39x) | -| Sample | Serialize | 1,744,871 | 481,801 | fory (3.62x) | -| Sample | Deserialize | 2,007,877 | 780,317 | fory (2.57x) | -| MediaContent | Serialize | 944,111 | 398,324 | fory (2.37x) | -| MediaContent | Deserialize | 1,457,065 | 675,724 | fory (2.16x) | -| StructList | Serialize | 1,981,716 | 351,853 | fory (5.63x) | -| StructList | Deserialize | 2,261,436 | 596,027 | fory (3.79x) | -| SampleList | Serialize | 426,153 | 46,590 | fory (9.15x) | -| SampleList | Deserialize | 479,900 | 99,694 | fory (4.81x) | -| MediaContentList | Serialize | 220,342 | 76,330 | fory (2.89x) | -| MediaContentList | Deserialize | 341,839 | 131,730 | fory (2.60x) | +| Datatype | Operation | Fory TPS | Protobuf TPS | Fastest | +| ---------------- | ----------- | ---------: | -----------: | ------------- | +| Struct | Serialize | 9,631,226 | 2,134,738 | fory (4.51x) | +| Struct | Deserialize | 10,449,715 | 4,968,658 | fory (2.10x) | +| Sample | Serialize | 2,468,767 | 536,410 | fory (4.60x) | +| Sample | Deserialize | 2,393,903 | 901,767 | fory (2.65x) | +| MediaContent | Serialize | 1,162,191 | 431,591 | fory (2.69x) | +| MediaContent | Deserialize | 2,005,785 | 767,396 | fory (2.61x) | +| StructList | Serialize | 2,851,755 | 374,020 | fory (7.62x) | +| StructList | Deserialize | 3,768,194 | 740,750 | fory (5.09x) | +| SampleList | Serialize | 568,405 | 48,603 | fory (11.69x) | +| SampleList | Deserialize | 546,914 | 111,151 | fory (4.92x) | +| MediaContentList | Serialize | 270,092 | 83,028 | fory (3.25x) | +| MediaContentList | Deserialize | 454,291 | 149,294 | fory (3.04x) | ## Serialized Size (bytes) | Datatype | Fory | Protobuf | | ---------------- | ---: | -------: | -| Struct | 58 | 61 | -| Sample | 446 | 377 | -| MediaContent | 365 | 307 | -| StructList | 184 | 315 | -| SampleList | 1980 | 1900 | -| MediaContentList | 1535 | 1550 | +| Struct | 57 | 61 | +| Sample | 445 | 377 | +| MediaContent | 362 | 307 | +| StructList | 182 | 315 | +| SampleList | 1978 | 1900 | +| MediaContentList | 1531 | 1550 | ## Per-workload Plots diff --git a/docs/benchmarks/dart/mediacontent.png b/docs/benchmarks/dart/mediacontent.png index 16f9b3a673..9538800879 100644 Binary files a/docs/benchmarks/dart/mediacontent.png and b/docs/benchmarks/dart/mediacontent.png differ diff --git a/docs/benchmarks/dart/mediacontentlist.png b/docs/benchmarks/dart/mediacontentlist.png index f88d84a9f0..b76bf70b4d 100644 Binary files a/docs/benchmarks/dart/mediacontentlist.png and b/docs/benchmarks/dart/mediacontentlist.png differ diff --git a/docs/benchmarks/dart/sample.png b/docs/benchmarks/dart/sample.png index bd182feab1..fcdc6bd1c5 100644 Binary files a/docs/benchmarks/dart/sample.png and b/docs/benchmarks/dart/sample.png differ diff --git a/docs/benchmarks/dart/samplelist.png b/docs/benchmarks/dart/samplelist.png index a3b7635019..c9d11d8753 100644 Binary files a/docs/benchmarks/dart/samplelist.png and b/docs/benchmarks/dart/samplelist.png differ diff --git a/docs/benchmarks/dart/struct.png b/docs/benchmarks/dart/struct.png index 84ac444fec..33b586d44f 100644 Binary files a/docs/benchmarks/dart/struct.png and b/docs/benchmarks/dart/struct.png differ diff --git a/docs/benchmarks/dart/structlist.png b/docs/benchmarks/dart/structlist.png index 1ed88798ea..7d4710a59b 100644 Binary files a/docs/benchmarks/dart/structlist.png and b/docs/benchmarks/dart/structlist.png differ diff --git a/docs/benchmarks/dart/throughput.png b/docs/benchmarks/dart/throughput.png index f3e6ace252..bdc34219d3 100644 Binary files a/docs/benchmarks/dart/throughput.png and b/docs/benchmarks/dart/throughput.png differ diff --git a/docs/specification/xlang_implementation_guide.md b/docs/specification/xlang_implementation_guide.md index e6814609e2..ba1ee0a9c3 100644 --- a/docs/specification/xlang_implementation_guide.md +++ b/docs/specification/xlang_implementation_guide.md @@ -55,7 +55,7 @@ For Dart, the runtime shape is centered on: - `RefWriter` - `RefReader` - `TypeResolver` -- `StructCodec` +- `StructSerializer` ## Runtime Ownership Model @@ -101,7 +101,6 @@ That operation-local state includes: - meta-string state - shared type-definition state - operation-local scratch state keyed by identity -- compatible struct slot state - logical object-graph depth Generated and hand-written serializers should treat these contexts as the only @@ -118,7 +117,6 @@ state in thread locals, globals, or serializer instance fields. - shared TypeDef write state - root `trackRef` mode - recursion depth and limits -- local struct slot state used by compatible writes It exposes one-shot primitive helpers such as: @@ -139,7 +137,6 @@ methods directly. - `MetaStringReader` - shared TypeDef read state - recursion depth and limits -- local struct slot state used by compatible reads It exposes matching one-shot primitive helpers such as: @@ -297,25 +294,26 @@ able to recover operation-local state after failures. ## Struct Compatibility -Struct-specific schema/version framing and compatible-field staging belong in -the struct serializer layer, not on `Fory` and not on the public serializer -API. +Struct-specific schema/version framing and compatible-field layout belong in the +struct serializer layer, not on `Fory` and not on the public serializer API. -In Dart that internal owner is `StructCodec`. +In Dart that internal owner is `StructSerializer`. -`StructCodec` is responsible for: +`StructSerializer` is responsible for: - schema-hash framing when compatibility mode is off and version checks are on - compatible-struct field remapping when compatibility mode is on -- caching compatible write and read layouts -- providing compatible write/read slot state to generated serializers -- remembering remote struct metadata after successful reads +- caching compatible read layouts +- skipping unknown compatible fields +- passing compatible read layouts explicitly to generated serializers When `Config.compatible` is enabled and the struct is marked evolving: - the wire type uses the compatible struct form - the runtime writes shared TypeDef metadata - reads map incoming fields by identifier and skip unknown fields +- generated serializers apply matched fields directly while preserving their own + object construction and default-value rules When `compatible` is disabled and `checkStructVersion` is enabled: @@ -415,7 +413,7 @@ different: 7. Preserve the separation between the root bitmap, per-object ref flags, type headers, and payload bytes. 8. Keep internal naming in the serialization domain. Prefer words like - `codec`, `binding`, `layout`, and `slots`; avoid RPC-style terms such as + `serializer`, `binding`, and `layout`; avoid RPC-style terms such as `session` or vague control-flow terms such as `plan`. 9. After any xlang protocol or ownership change, run the cross-language test matrix and update both this guide and