Object pools (#345)
This commit is contained in:
Родитель
7217cb8544
Коммит
e12bc42ead
|
@ -44,7 +44,7 @@ jobs:
|
|||
working-directory: src/FlatSharp.Compiler
|
||||
run: dotnet build -c Debug /p:SignAssembly=false
|
||||
|
||||
- name: Run FlatSharp.Compiler
|
||||
- name: Run FlatSharp.Compiler (E2E Tests)
|
||||
# You may pin to the exact commit or the version.
|
||||
# uses: Amadevus/pwsh-script@97a8b211a5922816aa8a69ced41fa32f23477186
|
||||
uses: Amadevus/pwsh-script@v2.0.3
|
||||
|
@ -58,7 +58,25 @@ jobs:
|
|||
--use-source-link `
|
||||
--format opencover `
|
||||
--target "dotnet" `
|
||||
--output e2etests.coverage.xml `
|
||||
--targetargs "src\FlatSharp.Compiler\bin\Debug\net7.0\FlatSharp.Compiler.dll --nullable-warnings false --normalize-field-names true --input `"$fbs`" -o src/tests/FlatSharpEndToEndTests"
|
||||
|
||||
- name: Run FlatSharp.Compiler (Pooling Tests)
|
||||
# You may pin to the exact commit or the version.
|
||||
# uses: Amadevus/pwsh-script@97a8b211a5922816aa8a69ced41fa32f23477186
|
||||
uses: Amadevus/pwsh-script@v2.0.3
|
||||
with:
|
||||
# PowerShell script to execute in Actions-hydrated context
|
||||
script: |
|
||||
$fbs = (gci -r src/tests/FlatSharpPoolableEndToEndTests/*.fbs) -join ";"
|
||||
coverlet `
|
||||
.\src\FlatSharp.Compiler\bin\Debug\net7.0 `
|
||||
--skipautoprops `
|
||||
--use-source-link `
|
||||
--format opencover `
|
||||
--target "dotnet" `
|
||||
--output pooling.coverage.xml `
|
||||
--targetargs "src\FlatSharp.Compiler\bin\Debug\net7.0\FlatSharp.Compiler.dll --nullable-warnings false --normalize-field-names true --gen-poolable true --input `"$fbs`" -o src/tests/FlatSharpPoolableEndToEndTests"
|
||||
|
||||
- name: Test
|
||||
working-directory: src
|
||||
|
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
working-directory: src/FlatSharp.Compiler
|
||||
run: dotnet build -c Release /p:SignAssembly=true
|
||||
|
||||
- name: Run FlatSharp.Compiler
|
||||
- name: Run FlatSharp.Compiler (E2E Tests)
|
||||
# You may pin to the exact commit or the version.
|
||||
# uses: Amadevus/pwsh-script@97a8b211a5922816aa8a69ced41fa32f23477186
|
||||
uses: Amadevus/pwsh-script@v2.0.3
|
||||
|
@ -42,6 +42,17 @@ jobs:
|
|||
script: |
|
||||
$fbs = (gci -r src/tests/FlatsharpEndToEndTests/*.fbs) -join ";"
|
||||
dotnet src/FlatSharp.Compiler/bin/Release/net7.0/FlatSharp.Compiler.dll --nullable-warnings false --normalize-field-names true --input `"$fbs`" -o src/tests/FlatSharpEndToEndTests
|
||||
|
||||
|
||||
- name: Run FlatSharp.Compiler (Pooling Tests)
|
||||
# You may pin to the exact commit or the version.
|
||||
# uses: Amadevus/pwsh-script@97a8b211a5922816aa8a69ced41fa32f23477186
|
||||
uses: Amadevus/pwsh-script@v2.0.3
|
||||
with:
|
||||
# PowerShell script to execute in Actions-hydrated context
|
||||
script: |
|
||||
$fbs = (gci -r src/tests/FlatSharpPoolableEndToEndTests/*.fbs) -join ";"
|
||||
dotnet src/FlatSharp.Compiler/bin/Release/net7.0/FlatSharp.Compiler.dll --nullable-warnings false --normalize-field-names true --gen-poolable true --input `"$fbs`" -o src/tests/FlatSharpPoolableEndToEndTests
|
||||
|
||||
- name: Build
|
||||
working-directory: src
|
||||
|
|
|
@ -21,6 +21,7 @@ namespace Microbench
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using FlatSharp;
|
||||
using FlatSharp.Internal;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
|
@ -30,6 +31,9 @@ namespace Microbench
|
|||
static Constants()
|
||||
{
|
||||
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime;
|
||||
#if POOLABLE
|
||||
ObjectPool.MaxToRetain = VectorLength;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static class StringTables
|
||||
|
@ -63,8 +67,8 @@ namespace Microbench
|
|||
{
|
||||
public static StructsTable SingleRef = new StructsTable { SingleRef = new RefStruct { Value = 1 }, SingleValue = default, };
|
||||
public static StructsTable SingleValue = new StructsTable { SingleValue = new ValueStruct { Value = 1 } };
|
||||
public static StructsTable VectorRef = new StructsTable { VecRef = Enumerable.Range(1, 30).Select(x => new RefStruct { Value = x }).ToList(), SingleValue = default, };
|
||||
public static StructsTable VectorValue = new StructsTable { VecValue = Enumerable.Range(1, 30).Select(x => new ValueStruct { Value = x }).ToList(), SingleValue = default, };
|
||||
public static StructsTable VectorRef = new StructsTable { VecRef = Enumerable.Range(1, VectorLength).Select(x => new RefStruct { Value = x }).ToList(), SingleValue = default, };
|
||||
public static StructsTable VectorValue = new StructsTable { VecValue = Enumerable.Range(1, VectorLength).Select(x => new ValueStruct { Value = x }).ToList(), SingleValue = default, };
|
||||
}
|
||||
|
||||
public static class SortedVectorTables
|
||||
|
|
|
@ -10,6 +10,12 @@
|
|||
<DefineConstants>$(DefineConstants);PUBLIC_IVTABLE</DefineConstants>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<FlatSharpCompilerPath>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)\..\..\FlatSharp.Compiler\bin\$(Configuration)\net7.0\FlatSharp.Compiler.dll'))</FlatSharpCompilerPath>
|
||||
<FlatSharpPoolable>false</FlatSharpPoolable>
|
||||
<FlatSharpNullable>false</FlatSharpNullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(FlatSharpPoolable)' == 'true' ">
|
||||
<DefineConstants>$(DefineConstants);POOLABLE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -17,9 +17,7 @@
|
|||
namespace Microbench
|
||||
{
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using System;
|
||||
using FlatSharp;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
|
@ -43,6 +41,8 @@ namespace Microbench
|
|||
length += table.SingleString!.Length;
|
||||
}
|
||||
|
||||
table.TryReturnToPool();
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
|
@ -74,21 +74,36 @@ namespace Microbench
|
|||
public int Parse_StructTable_SingleRef()
|
||||
{
|
||||
var st = StructsTable.Serializer.Parse(Constants.Buffers.StructTable_SingleRef);
|
||||
return st.SingleRef!.Value;
|
||||
var singleRef = st.SingleRef!;
|
||||
|
||||
int result = singleRef.Value;
|
||||
|
||||
//singleRef.TryReturnToPool();
|
||||
st.TryReturnToPool();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Parse_StructTable_SingleRef_WriteThrough()
|
||||
{
|
||||
var st = StructsTable.Serializer.Parse(Constants.Buffers.StructTable_SingleRef);
|
||||
st.SingleRef!.Value = 3;
|
||||
var singleRef = st.SingleRef!;
|
||||
singleRef.Value = 3;
|
||||
|
||||
//singleRef.TryReturnToPool();
|
||||
st.TryReturnToPool();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int Parse_StructTable_SingleValue()
|
||||
{
|
||||
var st = StructsTable.Serializer.Parse(Constants.Buffers.StructTable_SingleValue);
|
||||
return st.SingleValue.Value;
|
||||
var value = st.SingleValue.Value;
|
||||
|
||||
st.TryReturnToPool();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
|
@ -96,6 +111,8 @@ namespace Microbench
|
|||
{
|
||||
var st = StructsTable.Serializer.Parse(Constants.Buffers.StructTable_SingleValue);
|
||||
st.SingleValue = new ValueStruct { Value = 3 };
|
||||
|
||||
st.TryReturnToPool();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
|
@ -109,9 +126,14 @@ namespace Microbench
|
|||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
sum += vecRef[i].Value;
|
||||
var item = vecRef[i];
|
||||
sum += item.Value;
|
||||
//item.TryReturnToPool();
|
||||
}
|
||||
|
||||
//vecRef.TryReturnToPool();
|
||||
st.TryReturnToPool();
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
@ -125,8 +147,13 @@ namespace Microbench
|
|||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
vecRef[i].Value++;
|
||||
var item = vecRef[i];
|
||||
item.Value++;
|
||||
//item.TryReturnToPool();
|
||||
}
|
||||
|
||||
//vecRef.TryReturnToPool();
|
||||
st.TryReturnToPool();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
|
@ -143,6 +170,9 @@ namespace Microbench
|
|||
sum += vecValue[i].Value;
|
||||
}
|
||||
|
||||
//vecValue.TryReturnToPool();
|
||||
st.TryReturnToPool();
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
@ -160,6 +190,9 @@ namespace Microbench
|
|||
item.Value++;
|
||||
vecValue[i] = item;
|
||||
}
|
||||
|
||||
//vecValue.TryReturnToPool();
|
||||
st.TryReturnToPool();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
|
@ -176,6 +209,9 @@ namespace Microbench
|
|||
sum += vector[i].Accept<UnionVisitor, int>(visitor);
|
||||
}
|
||||
|
||||
//vector.TryReturnToPool();
|
||||
st.TryReturnToPool();
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
@ -193,6 +229,9 @@ namespace Microbench
|
|||
sum += vector[i].Accept<UnionVisitor, int>(visitor);
|
||||
}
|
||||
|
||||
//vector.TryReturnToPool();
|
||||
st.TryReturnToPool();
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
@ -210,6 +249,9 @@ namespace Microbench
|
|||
sum += vector[i].Accept<UnionVisitor, int>(visitor);
|
||||
}
|
||||
|
||||
//vector.TryReturnToPool();
|
||||
st.TryReturnToPool();
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
@ -252,6 +294,7 @@ namespace Microbench
|
|||
sum += (int)table.ULong;
|
||||
sum += table.UShort;
|
||||
|
||||
table.TryReturnToPool();
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
@ -275,9 +318,29 @@ namespace Microbench
|
|||
{
|
||||
length += vec[i].Length;
|
||||
}
|
||||
|
||||
//vec.TryReturnToPool();
|
||||
}
|
||||
|
||||
table.TryReturnToPool();
|
||||
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
#if POOLABLE
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void TryReturnToPool<T>(this T obj) where T : IPoolableObject
|
||||
{
|
||||
obj.ReturnToPool();
|
||||
}
|
||||
#else
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void TryReturnToPool<T>(this T obj)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,10 +55,10 @@ namespace Microbench
|
|||
//.AddHardwareCounters(HardwareCounter.BranchInstructions, HardwareCounter.BranchMispredictions)
|
||||
.AddJob(job.DontEnforcePowerPlan());
|
||||
|
||||
summaries.Add(BenchmarkRunner.Run(typeof(SerializeBenchmarks), config));
|
||||
//summaries.Add(BenchmarkRunner.Run(typeof(SerializeBenchmarks), config));
|
||||
summaries.Add(BenchmarkRunner.Run(typeof(ParseBenchmarks), config));
|
||||
summaries.Add(BenchmarkRunner.Run(typeof(SortedVectorBenchmarks), config));
|
||||
summaries.Add(BenchmarkRunner.Run(typeof(VTableBenchmarks), config));
|
||||
//summaries.Add(BenchmarkRunner.Run(typeof(SortedVectorBenchmarks), config));
|
||||
//summaries.Add(BenchmarkRunner.Run(typeof(VTableBenchmarks), config));
|
||||
|
||||
foreach (var item in summaries)
|
||||
{
|
||||
|
|
|
@ -12,12 +12,12 @@ attribute "fs_unsafeUnion";
|
|||
namespace Microbench;
|
||||
|
||||
// Tests reading and writing a string.
|
||||
table StringTable (fs_serializer:"Lazy") {
|
||||
table StringTable (fs_serializer:"Progressive") {
|
||||
SingleString : string;
|
||||
Vector : [string];
|
||||
}
|
||||
|
||||
table PrimitivesTable (fs_serializer:"Lazy") {
|
||||
table PrimitivesTable (fs_serializer:"Progressive") {
|
||||
Bool : bool;
|
||||
Byte : ubyte;
|
||||
SByte : byte;
|
||||
|
@ -34,7 +34,7 @@ table PrimitivesTable (fs_serializer:"Lazy") {
|
|||
struct RefStruct (fs_writeThrough) { Value : int; }
|
||||
struct ValueStruct (fs_valueStruct) { Value : int; }
|
||||
|
||||
table StructsTable (fs_serializer:"Lazy")
|
||||
table StructsTable (fs_serializer:"Progressive")
|
||||
{
|
||||
SingleRef : RefStruct;
|
||||
SingleValue : ValueStruct (fs_writeThrough, required);
|
||||
|
@ -45,7 +45,7 @@ table StructsTable (fs_serializer:"Lazy")
|
|||
table StringKey { Key : string (key); }
|
||||
table IntKey { Key : int (key); }
|
||||
|
||||
table SortedTable (fs_serializer:"Lazy")
|
||||
table SortedTable (fs_serializer:"Progressive")
|
||||
{
|
||||
Strings : [StringKey] (fs_vector:"IIndexedVector");
|
||||
Ints : [IntKey] (fs_vector:"IIndexedVector");
|
||||
|
@ -60,7 +60,7 @@ union UnsafeUnion (fs_unsafeUnion) { ValueStructA, ValueStructB, ValueStructC }
|
|||
union SafeUnion { ValueStructA, ValueStructB, ValueStructC }
|
||||
union MixedUnion { ValueStructA, ValueStructB, ValueStructC, Something : string }
|
||||
|
||||
table UnionTable (fs_serializer:"Lazy")
|
||||
table UnionTable (fs_serializer:"Progressive")
|
||||
{
|
||||
Unsafe : [ UnsafeUnion ];
|
||||
Safe : [ SafeUnion ];
|
||||
|
|
|
@ -38,6 +38,11 @@ public class CodeWriter
|
|||
this.builder.AppendLine(line);
|
||||
}
|
||||
|
||||
public void AppendInheritDoc()
|
||||
{
|
||||
this.AppendLine("/// <inheritdoc />");
|
||||
}
|
||||
|
||||
public void AppendSummaryComment(params string[] summaryParts)
|
||||
{
|
||||
this.AppendSummaryComment((IEnumerable<string>)summaryParts);
|
||||
|
|
|
@ -35,6 +35,9 @@ public record CompilerOptions
|
|||
[Option("nullable-warnings", Default = false, HelpText = "Emit full nullable annotations and enable warnings.")]
|
||||
public bool? NullableWarnings { get; set; }
|
||||
|
||||
[Option("gen-poolable", Hidden = false, Default = false, HelpText = "EXPERIMENTAL: Generate extra code to enable object pooling for allocation reductions.")]
|
||||
public bool? GeneratePoolableObjects { get; set; }
|
||||
|
||||
[Option("flatc-path", Hidden = true)]
|
||||
public string? FlatcPath { get; set; }
|
||||
|
||||
|
|
|
@ -95,6 +95,11 @@
|
|||
<FlatSharpNullable Condition=" '$(Nullable)' == 'enable' ">true</FlatSharpNullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(FlatSharpPoolable)' == '' ">
|
||||
<FlatSharpPoolable>false</FlatSharpPoolable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<PropertyGroup Condition=" '$(FlatSharpNameNormalization)' == '' ">
|
||||
<FlatSharpNameNormalization>true</FlatSharpNameNormalization>
|
||||
</PropertyGroup>
|
||||
|
@ -112,11 +117,11 @@
|
|||
</ProcessFlatSharpSchema>
|
||||
|
||||
<Message
|
||||
Text="dotnet $(CompilerPath) --nullable-warnings $(FlatSharpNullable) --normalize-field-names $(FlatSharpNameNormalization) --input "@(FlatSharpSchema)" --includes "$(Includes)" --output $(IntermediateOutputPath)"
|
||||
Text="dotnet $(CompilerPath) --nullable-warnings $(FlatSharpNullable) --normalize-field-names $(FlatSharpNameNormalization) --gen-poolable $(FlatSharpPoolable) --input "@(FlatSharpSchema)" --includes "$(Includes)" --output $(IntermediateOutputPath)"
|
||||
Importance="high" />
|
||||
|
||||
<Exec
|
||||
Command="dotnet $(CompilerPath) --nullable-warnings $(FlatSharpNullable) --normalize-field-names $(FlatSharpNameNormalization) --input "@(FlatSharpSchema)" --includes "$(Includes)" --output $(IntermediateOutputPath)"
|
||||
Command="dotnet $(CompilerPath) --nullable-warnings $(FlatSharpNullable) --normalize-field-names $(FlatSharpNameNormalization) --gen-poolable $(FlatSharpPoolable) --input "@(FlatSharpSchema)" --includes "$(Includes)" --output $(IntermediateOutputPath)"
|
||||
CustomErrorRegularExpression=".*" />
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -523,7 +523,7 @@ public class FlatSharpCompiler
|
|||
|
||||
foreach (var s in bfbs)
|
||||
{
|
||||
rootModel.UnionWith(ParseSchema(s, options, postProcessTransforms, mutators).ToRootModel());
|
||||
rootModel.UnionWith(ParseSchema(s, options, postProcessTransforms, mutators).ToRootModel(options));
|
||||
}
|
||||
|
||||
ErrorContext.Current.ThrowIfHasErrors();
|
||||
|
|
|
@ -63,7 +63,7 @@ public class Schema
|
|||
[FlatBufferItem(7)]
|
||||
public virtual IIndexedVector<string, SchemaFile>? FbsFiles { get; set; }
|
||||
|
||||
public RootModel ToRootModel()
|
||||
public RootModel ToRootModel(CompilerOptions options)
|
||||
{
|
||||
RootModel model = new RootModel(this.AdvancedFeatures);
|
||||
|
||||
|
@ -73,7 +73,7 @@ public class Schema
|
|||
{
|
||||
model.AddElement(enumModel);
|
||||
}
|
||||
else if (UnionSchemaModel.TryCreate(this, @enum, out var unionModel))
|
||||
else if (ValueUnionSchemaModel.TryCreate(this, @enum, options, out var unionModel))
|
||||
{
|
||||
model.AddElement(unionModel);
|
||||
}
|
||||
|
|
|
@ -74,13 +74,17 @@ public abstract class BaseReferenceTypeSchemaModel : BaseSchemaModel
|
|||
this.EmitDefaultConstrutor(writer, context);
|
||||
this.EmitDeserializationConstructor(writer);
|
||||
this.EmitCopyConstructor(writer, context);
|
||||
this.EmitPoolableObject(writer, context);
|
||||
|
||||
writer.AppendLine("static partial void OnStaticInitialize();");
|
||||
|
||||
writer.AppendLine("partial void OnInitialized(FlatBufferDeserializationContext? context);");
|
||||
|
||||
writer.AppendLine($"protected void {TableTypeModel.OnDeserializedMethodName}({nameof(FlatBufferDeserializationContext)}? context) => this.OnInitialized(context);");
|
||||
writer.AppendLine();
|
||||
writer.AppendLine($"protected void {TableTypeModel.OnDeserializedMethodName}({nameof(FlatBufferDeserializationContext)} context)");
|
||||
using (writer.WithBlock())
|
||||
{
|
||||
writer.AppendLine("this.OnInitialized(context);");
|
||||
}
|
||||
|
||||
foreach (var property in this.properties.OrderBy(x => x.Key))
|
||||
{
|
||||
|
@ -167,6 +171,18 @@ public abstract class BaseReferenceTypeSchemaModel : BaseSchemaModel
|
|||
writer.AppendLine("#pragma warning restore CS8618"); // nullable
|
||||
}
|
||||
|
||||
private void EmitPoolableObject(CodeWriter writer, CompileContext context)
|
||||
{
|
||||
if (context.Options.GeneratePoolableObjects == true)
|
||||
{
|
||||
writer.AppendLine("/// <inheritdoc />");
|
||||
writer.AppendLine("public virtual void ReturnToPool(bool unsafeForce = false)");
|
||||
using (writer.WithBlock())
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void EmitStaticConstructor(CodeWriter writer, CompileContext context)
|
||||
{
|
||||
writer.AppendLine($"static {this.Name}()");
|
||||
|
|
|
@ -31,4 +31,5 @@ public enum FlatBufferSchemaElementType
|
|||
StructVector = 10,
|
||||
ValueStructVector = 11,
|
||||
RpcCall = 12,
|
||||
PoolableUnion = 13,
|
||||
}
|
||||
|
|
|
@ -76,5 +76,11 @@ public class ReferenceStructSchemaModel : BaseReferenceTypeSchemaModel
|
|||
writer.AppendLine(attribute);
|
||||
writer.AppendLine("[System.Runtime.CompilerServices.CompilerGenerated]");
|
||||
writer.AppendLine($"public partial class {this.Name}");
|
||||
writer.AppendLine($" : object");
|
||||
|
||||
if (context.Options.GeneratePoolableObjects == true)
|
||||
{
|
||||
writer.AppendLine($" , IPoolableObject");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* Copyright 2021 James Courtney
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
using System.Linq;
|
||||
using FlatSharp.Compiler.Schema;
|
||||
using FlatSharp.CodeGen;
|
||||
using System;
|
||||
|
||||
namespace FlatSharp.Compiler.SchemaModel;
|
||||
|
||||
public class ReferenceUnionSchemaModel : BaseSchemaModel
|
||||
{
|
||||
private readonly FlatBufferEnum union;
|
||||
|
||||
internal ReferenceUnionSchemaModel(Schema.Schema schema, FlatBufferEnum union) : base(schema, union.Name, new FlatSharpAttributes(union.Attributes))
|
||||
{
|
||||
FlatSharpInternal.Assert(union.UnderlyingType.BaseType == BaseType.UType, "Expecting utype");
|
||||
|
||||
this.DeclaringFile = union.DeclarationFile;
|
||||
this.union = union;
|
||||
}
|
||||
|
||||
public override FlatBufferSchemaElementType ElementType => FlatBufferSchemaElementType.PoolableUnion;
|
||||
|
||||
public override string DeclaringFile { get; }
|
||||
|
||||
protected override void OnWriteCode(CodeWriter writer, CompileContext context)
|
||||
{
|
||||
List<(string resolvedType, EnumVal value, Type? propertyType)> innerTypes = new();
|
||||
foreach (var inner in this.union.Values.Select(x => x.Value))
|
||||
{
|
||||
// Skip "none".
|
||||
if (inner.Value == 0)
|
||||
{
|
||||
FlatSharpInternal.Assert(inner.Key == "NONE", "Expecting discriminator 0 to be 'None'");
|
||||
continue;
|
||||
}
|
||||
|
||||
FlatSharpInternal.Assert(inner.UnionType is not null, "Union type was null");
|
||||
|
||||
long discriminator = inner.Value;
|
||||
string typeName = inner.UnionType.ResolveTypeOrElementTypeName(this.Schema, this.Attributes);
|
||||
|
||||
Type? propertyClrType = null;
|
||||
if (context.CompilePass > CodeWritingPass.Initialization)
|
||||
{
|
||||
Type? previousType = context.PreviousAssembly?.GetType(this.FullName);
|
||||
FlatSharpInternal.Assert(previousType is not null, "PreviousType was null");
|
||||
|
||||
propertyClrType = previousType
|
||||
.GetProperty($"Item{inner.Value}", BindingFlags.Public | BindingFlags.Instance)?
|
||||
.PropertyType;
|
||||
|
||||
FlatSharpInternal.Assert(propertyClrType is not null, "Couldn't find property");
|
||||
}
|
||||
|
||||
innerTypes.Add((typeName, inner, propertyClrType));
|
||||
}
|
||||
|
||||
string interfaceName = $"IFlatBufferUnion<{string.Join(", ", innerTypes.Select(x => x.resolvedType))}>";
|
||||
|
||||
writer.AppendSummaryComment(this.union.Documentation);
|
||||
writer.AppendLine("[System.Runtime.CompilerServices.CompilerGenerated]");
|
||||
writer.AppendLine($"public partial class {this.Name} : object, {interfaceName}, IPoolableObject");
|
||||
using (writer.WithBlock())
|
||||
{
|
||||
// Generate an internal type enum.
|
||||
writer.AppendLine("public enum ItemKind : byte");
|
||||
using (writer.WithBlock())
|
||||
{
|
||||
foreach (var item in this.union.Values)
|
||||
{
|
||||
writer.AppendLine($"{item.Value.Key} = {item.Value.Value},");
|
||||
}
|
||||
}
|
||||
|
||||
writer.AppendLine();
|
||||
writer.AppendLine("protected int discriminator;");
|
||||
|
||||
writer.AppendLine();
|
||||
writer.AppendLine("public ItemKind Kind => (ItemKind)this.Discriminator;");
|
||||
|
||||
writer.AppendLine();
|
||||
writer.AppendLine("public byte Discriminator => (byte)this.discriminator;");
|
||||
|
||||
foreach (var item in innerTypes)
|
||||
{
|
||||
this.WriteConstructor(writer, item.resolvedType, item.value, item.propertyType);
|
||||
this.AddUnionMember(writer, item.resolvedType, item.value, item.propertyType, context);
|
||||
}
|
||||
|
||||
this.WriteDefaultConstructor(writer);
|
||||
this.WriteReturnToPool(writer);
|
||||
this.WriteAcceptMethod(writer, innerTypes);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUnionMember(CodeWriter writer, string resolvedType, EnumVal value, Type? propertyClrType, CompileContext context)
|
||||
{
|
||||
writer.AppendLine();
|
||||
writer.AppendLine($"private {resolvedType}{(propertyClrType?.IsValueType == false ? "?" : string.Empty)} value_{value.Value};");
|
||||
|
||||
writer.AppendLine();
|
||||
writer.AppendLine($"public {resolvedType} Item{value.Value}");
|
||||
using (writer.WithBlock())
|
||||
{
|
||||
writer.AppendLine("get");
|
||||
using (writer.WithBlock())
|
||||
{
|
||||
writer.AppendLine($"if (this.Discriminator != {value.Value})");
|
||||
using (writer.WithBlock())
|
||||
{
|
||||
writer.AppendLine("throw new InvalidOperationException();");
|
||||
}
|
||||
|
||||
writer.AppendLine($"return this.value_{value.Value}!;");
|
||||
}
|
||||
|
||||
writer.AppendLine("protected set");
|
||||
using (writer.WithBlock())
|
||||
{
|
||||
writer.AppendLine($"this.value_{value.Value} = value;");
|
||||
}
|
||||
}
|
||||
|
||||
string notNullWhen = string.Empty;
|
||||
if (context.Options.NullableWarnings == true)
|
||||
{
|
||||
notNullWhen = $"[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] ";
|
||||
}
|
||||
|
||||
writer.AppendLine();
|
||||
writer.AppendLine($"public bool TryGet({notNullWhen} out {resolvedType} value)");
|
||||
using (writer.WithBlock())
|
||||
{
|
||||
writer.AppendLine($"if (this.Discriminator != {value.Value})");
|
||||
using (writer.WithBlock())
|
||||
{
|
||||
writer.AppendLine("value = default;");
|
||||
writer.AppendLine("return false;");
|
||||
}
|
||||
|
||||
writer.AppendLine($"value = this.value_{value.Value}!;");
|
||||
writer.AppendLine("return true;");
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteReturnToPool(CodeWriter writer)
|
||||
{
|
||||
writer.AppendInheritDoc();
|
||||
writer.AppendLine($"public virtual void ReturnToPool(bool unsafeForce = false) {{ }}");
|
||||
}
|
||||
|
||||
private void WriteAcceptMethod(
|
||||
CodeWriter writer,
|
||||
List<(string resolvedType, EnumVal value, Type? propertyType)> components)
|
||||
{
|
||||
string visitorBaseType = $"IFlatBufferUnionVisitor<TReturn, {string.Join(", ", components.Select(x => x.resolvedType))}>";
|
||||
|
||||
writer.AppendSummaryComment("A convenience interface for implementing a visitor.");
|
||||
writer.AppendLine($"public interface Visitor<TReturn> : {visitorBaseType} {{ }}");
|
||||
|
||||
writer.AppendSummaryComment("Accepts a visitor into this FlatBufferUnion.");
|
||||
writer.AppendLine($"public TReturn Accept<TVisitor, TReturn>(TVisitor visitor)");
|
||||
writer.AppendLine($" where TVisitor : {visitorBaseType}");
|
||||
using (writer.WithBlock())
|
||||
{
|
||||
writer.AppendLine("var disc = this.Discriminator;");
|
||||
writer.AppendLine("switch (disc)");
|
||||
using (writer.WithBlock())
|
||||
{
|
||||
foreach (var item in components)
|
||||
{
|
||||
long index = item.value.Value;
|
||||
writer.AppendLine($"case {index}: return visitor.Visit(this.value_{item.value.Value});");
|
||||
}
|
||||
|
||||
writer.AppendLine($"default: throw new {typeof(InvalidOperationException).GetCompilableTypeName()}(\"Unexpected discriminator: \" + disc);");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteConstructor(CodeWriter writer, string resolvedType, EnumVal unionValue, Type? propertyType)
|
||||
{
|
||||
writer.AppendLine($"public {this.Name}({resolvedType} value)");
|
||||
using (writer.WithBlock())
|
||||
{
|
||||
if (propertyType?.IsValueType == false)
|
||||
{
|
||||
writer.AppendLine("if (value is null)");
|
||||
using (writer.WithBlock())
|
||||
{
|
||||
writer.AppendLine("throw new ArgumentNullException(nameof(value));");
|
||||
}
|
||||
}
|
||||
|
||||
writer.AppendLine($"this.discriminator = {unionValue.Value};");
|
||||
writer.AppendLine($"this.Item{unionValue.Value} = value;");
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteDefaultConstructor(CodeWriter writer)
|
||||
{
|
||||
writer.AppendLine($"protected {this.Name}()");
|
||||
using (writer.WithBlock())
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -99,6 +99,12 @@ public class TableSchemaModel : BaseReferenceTypeSchemaModel
|
|||
using (writer.IncreaseIndent())
|
||||
{
|
||||
writer.AppendLine(": object");
|
||||
|
||||
if (context.Options.GeneratePoolableObjects == true)
|
||||
{
|
||||
writer.AppendLine(", IPoolableObject");
|
||||
}
|
||||
|
||||
if (this.Attributes.DeserializationOption is not null && context.CompilePass >= CodeWritingPass.SerializerAndRpcGeneration)
|
||||
{
|
||||
writer.AppendLine($", {nameof(IFlatBufferSerializable)}<{this.FullName}>");
|
||||
|
|
|
@ -19,14 +19,15 @@ using FlatSharp.Compiler.Schema;
|
|||
using FlatSharp.CodeGen;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.IO;
|
||||
using System.Reflection.Metadata;
|
||||
|
||||
namespace FlatSharp.Compiler.SchemaModel;
|
||||
|
||||
public class UnionSchemaModel : BaseSchemaModel
|
||||
public class ValueUnionSchemaModel : BaseSchemaModel
|
||||
{
|
||||
private readonly FlatBufferEnum union;
|
||||
|
||||
private UnionSchemaModel(Schema.Schema schema, FlatBufferEnum union) : base(schema, union.Name, new FlatSharpAttributes(union.Attributes))
|
||||
private ValueUnionSchemaModel(Schema.Schema schema, FlatBufferEnum union) : base(schema, union.Name, new FlatSharpAttributes(union.Attributes))
|
||||
{
|
||||
FlatSharpInternal.Assert(union.UnderlyingType.BaseType == BaseType.UType, "Expecting utype");
|
||||
|
||||
|
@ -36,7 +37,11 @@ public class UnionSchemaModel : BaseSchemaModel
|
|||
this.AttributeValidator.UnsafeUnionValidator = b => AttributeValidationResult.Valid;
|
||||
}
|
||||
|
||||
public static bool TryCreate(Schema.Schema schema, FlatBufferEnum union, [NotNullWhen(true)] out UnionSchemaModel? model)
|
||||
public static bool TryCreate(
|
||||
Schema.Schema schema,
|
||||
FlatBufferEnum union,
|
||||
CompilerOptions context,
|
||||
[NotNullWhen(true)] out BaseSchemaModel? model)
|
||||
{
|
||||
if (union.UnderlyingType.BaseType != BaseType.UType)
|
||||
{
|
||||
|
@ -44,7 +49,15 @@ public class UnionSchemaModel : BaseSchemaModel
|
|||
return false;
|
||||
}
|
||||
|
||||
model = new UnionSchemaModel(schema, union);
|
||||
if (context.GeneratePoolableObjects == true)
|
||||
{
|
||||
model = new ReferenceUnionSchemaModel(schema, union);
|
||||
}
|
||||
else
|
||||
{
|
||||
model = new ValueUnionSchemaModel(schema, union);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ namespace FlatSharp;
|
|||
/// A context that FlatSharp-deserialized classes will pass to their parent
|
||||
/// object on construction, if the parent object defines a constructor that accepts this object.
|
||||
/// </summary>
|
||||
public class FlatBufferDeserializationContext
|
||||
public struct FlatBufferDeserializationContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new FlatSharpConstructorContext with the given deserialization option.
|
||||
|
|
|
@ -114,22 +114,36 @@ internal class GeneratedSerializerWrapper<T> : ISerializer<T>, ISerializer where
|
|||
var parseArgs = new GeneratedSerializerParseArguments(0, this.remainingDepthLimit);
|
||||
var inner = this.innerSerializer;
|
||||
|
||||
T item;
|
||||
|
||||
switch (option ?? this.option)
|
||||
{
|
||||
case FlatBufferDeserializationOption.Lazy:
|
||||
return buffer.InvokeLazyParse(inner, in parseArgs);
|
||||
item = buffer.InvokeLazyParse(inner, in parseArgs);
|
||||
break;
|
||||
|
||||
case FlatBufferDeserializationOption.Greedy:
|
||||
return buffer.InvokeGreedyParse(inner, in parseArgs);
|
||||
item = buffer.InvokeGreedyParse(inner, in parseArgs);
|
||||
break;
|
||||
|
||||
case FlatBufferDeserializationOption.GreedyMutable:
|
||||
return buffer.InvokeGreedyMutableParse(inner, in parseArgs);
|
||||
item = buffer.InvokeGreedyMutableParse(inner, in parseArgs);
|
||||
break;
|
||||
|
||||
case FlatBufferDeserializationOption.Progressive:
|
||||
return buffer.InvokeProgressiveParse(inner, in parseArgs);
|
||||
item = buffer.InvokeProgressiveParse(inner, in parseArgs);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException("Unexpected deserialization mode: " + this.option);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Unexpected deserialization mode: " + this.option);
|
||||
if (item is IPoolableObjectDebug deserializedObject)
|
||||
{
|
||||
deserializedObject.IsRoot = true;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
object ISerializer.Parse<TInputBuffer>(TInputBuffer buffer, FlatBufferDeserializationOption? option) => this.Parse(buffer, option);
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright 2021 James Courtney
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
namespace FlatSharp
|
||||
{
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
/// Defines an object pool that FlatSharp may use to reduce allocations.
|
||||
/// </summary>
|
||||
public interface IObjectPool
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to get an item from the pool.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of item.</typeparam>
|
||||
/// <param name="value">The value, as an output parameter.</param>
|
||||
/// <returns>True if the value was returned. False otherwise.</returns>
|
||||
bool TryGet<T>([NotNullWhen(true)] out T? value);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an item to the pool. FlatSharp users should never use this method directly.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of item.</typeparam>
|
||||
/// <param name="item">The item to return.</param>
|
||||
void Return<T>(T item);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// A FlatSharp poolable object.
|
||||
/// </summary>
|
||||
public interface IPoolableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to return this object to the pool.
|
||||
/// </summary>
|
||||
/// <param name="unsafeForce">Force this back to the pool, regardless of internal consistency rules.</param>
|
||||
void ReturnToPool(bool unsafeForce = false);
|
||||
}
|
||||
}
|
||||
|
||||
namespace FlatSharp.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug information for poolable objects.
|
||||
/// </summary>
|
||||
public interface IPoolableObjectDebug
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates if this object is the root of the parse tree.
|
||||
/// </summary>
|
||||
bool IsRoot { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright 2021 James Courtney
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace FlatSharp.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal disposal state.
|
||||
/// </summary>
|
||||
public static class ObjectPoolDisposalState
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension method to indicate if we should always dispose or not.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool ShouldReturnToPool(this FlatBufferDeserializationOption option, bool force)
|
||||
{
|
||||
return // ObjectPool.Instance is not null &&
|
||||
(force || option == FlatBufferDeserializationOption.Lazy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace FlatSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// A static singleton that defines the FlatSharp object pool to use.
|
||||
/// </summary>
|
||||
public static class ObjectPool
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the soft limit on the maximum number of objects FlatSharp should retain. Note that this
|
||||
/// limit applies per-type and is a soft limit, meaning it may be exceeded temporarily.
|
||||
/// </summary>
|
||||
public static int MaxToRetain { get; set; }
|
||||
|
||||
#if DEBUG
|
||||
public static IObjectPool? Instance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get an instance of <typeparamref name="T"/> from the pool.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGet<T>([NotNullWhen(true)] out T? value)
|
||||
{
|
||||
value = default;
|
||||
return Instance?.TryGet<T>(out value) == true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the given item to the pool.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Return<T>(T item)
|
||||
{
|
||||
Instance?.Return(item);
|
||||
}
|
||||
#else
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get an instance of <typeparamref name="T"/> from the pool.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGet<T>([NotNullWhen(true)] out T? value)
|
||||
{
|
||||
return DefaultObjectPool.TryGet(out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the given item to the pool.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Return<T>(T item)
|
||||
{
|
||||
DefaultObjectPool.Return(item);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A default implementation of the <see cref="IObjectPool"/> interface.
|
||||
/// </summary>
|
||||
[ExcludeFromCodeCoverage]
|
||||
public static class DefaultObjectPool
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Return<T>(T item)
|
||||
{
|
||||
Pool<T>.Return(item, ObjectPool.MaxToRetain);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGet<T>([NotNullWhen(true)] out T? value)
|
||||
{
|
||||
return Pool<T>.TryGet(out value);
|
||||
}
|
||||
|
||||
private static class Pool<T>
|
||||
{
|
||||
private static readonly ConcurrentQueue<T> pool = new();
|
||||
|
||||
/// <summary>
|
||||
/// ConcurrentQueue's Count property is quite slow. FastCount just uses interlocked operations.
|
||||
/// </summary>
|
||||
private static int FastCount = 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGet([NotNullWhen(true)] out T? item)
|
||||
{
|
||||
item = default;
|
||||
|
||||
if (FastCount > 0)
|
||||
{
|
||||
if (pool.TryDequeue(out item))
|
||||
{
|
||||
Interlocked.Decrement(ref FastCount);
|
||||
Debug.Assert(item is not null);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Return(T item, int limit)
|
||||
{
|
||||
if (FastCount < limit)
|
||||
{
|
||||
pool.Enqueue(item);
|
||||
Interlocked.Increment(ref FastCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ using System.Runtime.CompilerServices;
|
|||
[assembly: InternalsVisibleTo("FlatSharp, PublicKey=0024000004800000940000000602000000240000525341310004000001000100898185ce69dca04430ab296e094cd7eb6c66f5a3cfb0631ef64586fa183f0cb5ca64c47539a3a3c6351a9cf8d976a8d94350af430d5adc10536b3904cc1d6ecaaf3d0cb708aa318c559625f05d3b2d89da1c2bb323bb40e36dcf9245f21c3a4b6793c56ffface5e6e18290afb13c7eac1ea9c7a0c22f289c622bfa7b247d81a2")]
|
||||
[assembly: InternalsVisibleTo("FlatSharpTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100898185ce69dca04430ab296e094cd7eb6c66f5a3cfb0631ef64586fa183f0cb5ca64c47539a3a3c6351a9cf8d976a8d94350af430d5adc10536b3904cc1d6ecaaf3d0cb708aa318c559625f05d3b2d89da1c2bb323bb40e36dcf9245f21c3a4b6793c56ffface5e6e18290afb13c7eac1ea9c7a0c22f289c622bfa7b247d81a2")]
|
||||
[assembly: InternalsVisibleTo("FlatSharpEndToEndTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100898185ce69dca04430ab296e094cd7eb6c66f5a3cfb0631ef64586fa183f0cb5ca64c47539a3a3c6351a9cf8d976a8d94350af430d5adc10536b3904cc1d6ecaaf3d0cb708aa318c559625f05d3b2d89da1c2bb323bb40e36dcf9245f21c3a4b6793c56ffface5e6e18290afb13c7eac1ea9c7a0c22f289c622bfa7b247d81a2")]
|
||||
[assembly: InternalsVisibleTo("PoolingTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100898185ce69dca04430ab296e094cd7eb6c66f5a3cfb0631ef64586fa183f0cb5ca64c47539a3a3c6351a9cf8d976a8d94350af430d5adc10536b3904cc1d6ecaaf3d0cb708aa318c559625f05d3b2d89da1c2bb323bb40e36dcf9245f21c3a4b6793c56ffface5e6e18290afb13c7eac1ea9c7a0c22f289c622bfa7b247d81a2")]
|
||||
[assembly: InternalsVisibleTo("FlatSharpCompilerTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100898185ce69dca04430ab296e094cd7eb6c66f5a3cfb0631ef64586fa183f0cb5ca64c47539a3a3c6351a9cf8d976a8d94350af430d5adc10536b3904cc1d6ecaaf3d0cb708aa318c559625f05d3b2d89da1c2bb323bb40e36dcf9245f21c3a4b6793c56ffface5e6e18290afb13c7eac1ea9c7a0c22f289c622bfa7b247d81a2")]
|
||||
[assembly: InternalsVisibleTo("FlatSharp.Compiler, PublicKey=0024000004800000940000000602000000240000525341310004000001000100898185ce69dca04430ab296e094cd7eb6c66f5a3cfb0631ef64586fa183f0cb5ca64c47539a3a3c6351a9cf8d976a8d94350af430d5adc10536b3904cc1d6ecaaf3d0cb708aa318c559625f05d3b2d89da1c2bb323bb40e36dcf9245f21c3a4b6793c56ffface5e6e18290afb13c7eac1ea9c7a0c22f289c622bfa7b247d81a2")]
|
||||
[assembly: InternalsVisibleTo("ExperimentalBenchmark, PublicKey=0024000004800000940000000602000000240000525341310004000001000100898185ce69dca04430ab296e094cd7eb6c66f5a3cfb0631ef64586fa183f0cb5ca64c47539a3a3c6351a9cf8d976a8d94350af430d5adc10536b3904cc1d6ecaaf3d0cb708aa318c559625f05d3b2d89da1c2bb323bb40e36dcf9245f21c3a4b6793c56ffface5e6e18290afb13c7eac1ea9c7a0c22f289c622bfa7b247d81a2")]
|
||||
|
@ -32,6 +33,7 @@ using System.Runtime.CompilerServices;
|
|||
[assembly: InternalsVisibleTo("FlatSharpTests")]
|
||||
[assembly: InternalsVisibleTo("FlatSharpCompilerTests")]
|
||||
[assembly: InternalsVisibleTo("FlatSharpEndToEndTests")]
|
||||
[assembly: InternalsVisibleTo("PoolingTests")]
|
||||
[assembly: InternalsVisibleTo("FlatSharp.Compiler")]
|
||||
[assembly: InternalsVisibleTo("ExperimentalBenchmark")]
|
||||
[assembly: InternalsVisibleTo("Benchmark")]
|
||||
|
|
|
@ -46,7 +46,7 @@ public static class VectorCloneHelpers
|
|||
|
||||
[return: NotNullIfNotNull("source")]
|
||||
public static IList<T>? CloneVectorOfUnion<T>(IList<T>? source, CloneCallback<T> cloneItem)
|
||||
where T : struct, IFlatBufferUnion
|
||||
where T : IFlatBufferUnion
|
||||
{
|
||||
if (source is null)
|
||||
{
|
||||
|
@ -86,7 +86,7 @@ public static class VectorCloneHelpers
|
|||
|
||||
[return: NotNullIfNotNull("source")]
|
||||
public static IReadOnlyList<T>? CloneVectorOfUnion<T>(IReadOnlyList<T>? source, CloneCallback<T> cloneItem)
|
||||
where T : struct, IFlatBufferUnion
|
||||
where T : IFlatBufferUnion
|
||||
{
|
||||
if (source is null)
|
||||
{
|
||||
|
|
|
@ -14,25 +14,49 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System.Threading;
|
||||
|
||||
namespace FlatSharp.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// An implementation of IIndexedVector for use after deserializing an object. This class is not intended to be used
|
||||
/// directly -- only from code generated by FlatSharp.
|
||||
/// </summary>
|
||||
public class FlatBufferIndexedVector<TKey, TValue, TInputBuffer, TVectorItemAccessor> : IIndexedVector<TKey, TValue>
|
||||
public class FlatBufferIndexedVector<TKey, TValue, TInputBuffer, TVectorItemAccessor>
|
||||
: IIndexedVector<TKey, TValue>
|
||||
|
||||
where TValue : class, ISortableTable<TKey>
|
||||
where TKey : notnull
|
||||
where TInputBuffer : IInputBuffer
|
||||
where TVectorItemAccessor : IVectorItemAccessor<TValue, TInputBuffer>
|
||||
{
|
||||
private readonly FlatBufferVectorBase<TValue, TInputBuffer, TVectorItemAccessor> vector;
|
||||
private FlatBufferDeserializationOption deserializationOption;
|
||||
private FlatBufferVectorBase<TValue, TInputBuffer, TVectorItemAccessor> vector;
|
||||
|
||||
public FlatBufferIndexedVector(FlatBufferVectorBase<TValue, TInputBuffer, TVectorItemAccessor> vector)
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
private FlatBufferIndexedVector()
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
{
|
||||
}
|
||||
|
||||
private void Initialize(FlatBufferVectorBase<TValue, TInputBuffer, TVectorItemAccessor> vector)
|
||||
{
|
||||
this.deserializationOption = vector.DeserializationOption;
|
||||
this.vector = vector;
|
||||
}
|
||||
|
||||
public static FlatBufferIndexedVector<TKey, TValue, TInputBuffer, TVectorItemAccessor> GetOrCreate(
|
||||
FlatBufferVectorBase<TValue, TInputBuffer, TVectorItemAccessor> vector)
|
||||
{
|
||||
if (!ObjectPool.TryGet<FlatBufferIndexedVector<TKey, TValue, TInputBuffer, TVectorItemAccessor>>(out var item))
|
||||
{
|
||||
item = new();
|
||||
}
|
||||
|
||||
item.Initialize(vector);
|
||||
return item;
|
||||
}
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
|
@ -103,4 +127,18 @@ public class FlatBufferIndexedVector<TKey, TValue, TInputBuffer, TVectorItemAcce
|
|||
{
|
||||
throw new NotMutableException();
|
||||
}
|
||||
|
||||
public void ReturnToPool(bool force = false)
|
||||
{
|
||||
if (this.deserializationOption.ShouldReturnToPool(force))
|
||||
{
|
||||
var vec = Interlocked.Exchange(ref this.vector!, null);
|
||||
if (vec is not null)
|
||||
{
|
||||
vec.ReturnToPool(true);
|
||||
this.vector = null!;
|
||||
ObjectPool.Return(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,24 +14,47 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System.Threading;
|
||||
|
||||
namespace FlatSharp.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="IIndexedVector{TKey, TValue}"/> implementation that loads data progressively.
|
||||
/// </summary>
|
||||
public sealed class FlatBufferProgressiveIndexedVector<TKey, TValue, TInputBuffer, TVectorItemAccessor> : IIndexedVector<TKey, TValue>
|
||||
public sealed class FlatBufferProgressiveIndexedVector<TKey, TValue, TInputBuffer, TVectorItemAccessor>
|
||||
: IIndexedVector<TKey, TValue>
|
||||
|
||||
where TValue : class, ISortableTable<TKey>
|
||||
where TKey : notnull
|
||||
where TInputBuffer : IInputBuffer
|
||||
where TVectorItemAccessor : IVectorItemAccessor<TValue, TInputBuffer>
|
||||
{
|
||||
private readonly Dictionary<TKey, TValue?> backingDictionary;
|
||||
private readonly FlatBufferProgressiveVector<TValue, TInputBuffer, TVectorItemAccessor> backingVector;
|
||||
private readonly Dictionary<TKey, TValue?> backingDictionary = new();
|
||||
private FlatBufferProgressiveVector<TValue, TInputBuffer, TVectorItemAccessor> backingVector;
|
||||
|
||||
public FlatBufferProgressiveIndexedVector(FlatBufferVectorBase<TValue, TInputBuffer, TVectorItemAccessor> items)
|
||||
private FlatBufferDeserializationOption deserializationOption;
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
private FlatBufferProgressiveIndexedVector()
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
{
|
||||
this.backingDictionary = new Dictionary<TKey, TValue?>();
|
||||
this.backingVector = new FlatBufferProgressiveVector<TValue, TInputBuffer, TVectorItemAccessor>(items);
|
||||
}
|
||||
|
||||
private void Initialize(FlatBufferVectorBase<TValue, TInputBuffer, TVectorItemAccessor> items)
|
||||
{
|
||||
this.deserializationOption = items.DeserializationOption;
|
||||
this.backingVector = FlatBufferProgressiveVector<TValue, TInputBuffer, TVectorItemAccessor>.GetOrCreate(items);
|
||||
}
|
||||
|
||||
public static FlatBufferProgressiveIndexedVector<TKey, TValue, TInputBuffer, TVectorItemAccessor> GetOrCreate(FlatBufferVectorBase<TValue, TInputBuffer, TVectorItemAccessor> items)
|
||||
{
|
||||
if (!ObjectPool.TryGet<FlatBufferProgressiveIndexedVector<TKey, TValue, TInputBuffer, TVectorItemAccessor>>(out var item))
|
||||
{
|
||||
item = new();
|
||||
}
|
||||
|
||||
item.Initialize(items);
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -135,4 +158,19 @@ public sealed class FlatBufferProgressiveIndexedVector<TKey, TValue, TInputBuffe
|
|||
{
|
||||
throw new NotMutableException();
|
||||
}
|
||||
|
||||
public void ReturnToPool(bool force = false)
|
||||
{
|
||||
if (this.deserializationOption.ShouldReturnToPool(force))
|
||||
{
|
||||
var backingVector = Interlocked.Exchange(ref this.backingVector!, null);
|
||||
if (backingVector is not null)
|
||||
{
|
||||
backingVector.ReturnToPool(true);
|
||||
this.backingDictionary.Clear();
|
||||
|
||||
ObjectPool.Return(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,13 +14,20 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System.Buffers;
|
||||
using System.Threading;
|
||||
|
||||
namespace FlatSharp.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// A vector implementation that is filled on demand. Optimized
|
||||
/// for data locality, random access, and reasonably low memory overhead.
|
||||
/// </summary>
|
||||
public sealed class FlatBufferProgressiveVector<T, TInputBuffer, TVectorItemAccessor> : IList<T>, IReadOnlyList<T>
|
||||
public sealed class FlatBufferProgressiveVector<T, TInputBuffer, TVectorItemAccessor>
|
||||
: IList<T>
|
||||
, IReadOnlyList<T>
|
||||
, IPoolableObject
|
||||
|
||||
where T : notnull
|
||||
where TInputBuffer : IInputBuffer
|
||||
where TVectorItemAccessor : IVectorItemAccessor<T, TInputBuffer>
|
||||
|
@ -31,15 +38,34 @@ public sealed class FlatBufferProgressiveVector<T, TInputBuffer, TVectorItemAcce
|
|||
// A semi-sparse array. Each row contains ChunkSize items.
|
||||
// This approach allows fast access while not broadly over allocating.
|
||||
// Using "mini arrays" also ensures good sequential access performance.
|
||||
private readonly T?[]?[] items;
|
||||
private readonly FlatBufferVectorBase<T, TInputBuffer, TVectorItemAccessor> innerVector;
|
||||
private T?[]?[] items;
|
||||
private FlatBufferVectorBase<T, TInputBuffer, TVectorItemAccessor> innerVector;
|
||||
|
||||
public FlatBufferProgressiveVector(
|
||||
FlatBufferVectorBase<T, TInputBuffer, TVectorItemAccessor> innerVector)
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
private FlatBufferProgressiveVector()
|
||||
{
|
||||
}
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
|
||||
private void Initialize(FlatBufferVectorBase<T, TInputBuffer, TVectorItemAccessor> innerVector)
|
||||
{
|
||||
this.Count = innerVector.Count;
|
||||
this.innerVector = innerVector;
|
||||
this.items = new T[(innerVector.Count / ChunkSize) + 1][];
|
||||
this.DeserializationOption = innerVector.DeserializationOption;
|
||||
int minLength = (int)(innerVector.Count / ChunkSize + 1);
|
||||
this.items = ArrayPool<T?[]?>.Shared.Rent(minLength);
|
||||
}
|
||||
|
||||
public static FlatBufferProgressiveVector<T, TInputBuffer, TVectorItemAccessor> GetOrCreate(FlatBufferVectorBase<T, TInputBuffer, TVectorItemAccessor> innerVector)
|
||||
{
|
||||
if (!ObjectPool.TryGet<FlatBufferProgressiveVector<T, TInputBuffer, TVectorItemAccessor>>(out var item))
|
||||
{
|
||||
item = new FlatBufferProgressiveVector<T, TInputBuffer, TVectorItemAccessor>();
|
||||
}
|
||||
|
||||
item.Initialize(innerVector);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -86,10 +112,12 @@ public sealed class FlatBufferProgressiveVector<T, TInputBuffer, TVectorItemAcce
|
|||
}
|
||||
}
|
||||
|
||||
public int Count { get; }
|
||||
public int Count { get; private set; }
|
||||
|
||||
public bool IsReadOnly => true;
|
||||
|
||||
internal FlatBufferDeserializationOption DeserializationOption { get; private set; }
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
throw new NotMutableException("FlatBufferVector does not allow adding items.");
|
||||
|
@ -182,7 +210,7 @@ public sealed class FlatBufferProgressiveVector<T, TInputBuffer, TVectorItemAcce
|
|||
|
||||
if (row is null)
|
||||
{
|
||||
row = new T[ChunkSize];
|
||||
row = ArrayPool<T>.Shared.Rent((int)ChunkSize);
|
||||
items[rowIndex] = row;
|
||||
|
||||
// For value types -- we can't rely on null to tell
|
||||
|
@ -199,4 +227,48 @@ public sealed class FlatBufferProgressiveVector<T, TInputBuffer, TVectorItemAcce
|
|||
|
||||
return row;
|
||||
}
|
||||
|
||||
public void ReturnToPool(bool force = false)
|
||||
{
|
||||
if (this.DeserializationOption.ShouldReturnToPool(force))
|
||||
{
|
||||
T?[]?[]? items = Interlocked.Exchange(ref this.items!, null);
|
||||
|
||||
if (items is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < items.Length; ++i)
|
||||
{
|
||||
T?[]? block = items[i];
|
||||
|
||||
if (block is not null)
|
||||
{
|
||||
// For reference types, we should try to return them.
|
||||
if (!typeof(T).IsValueType && typeof(IPoolableObject).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
for (int j = 0; j < block.Length; ++j)
|
||||
{
|
||||
if (block[j] is IPoolableObject poolable)
|
||||
{
|
||||
poolable.ReturnToPool(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArrayPool<T?>.Shared.Return(block, true);
|
||||
items[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
ArrayPool<T?[]?>.Shared.Return(items);
|
||||
|
||||
this.Count = 0;
|
||||
this.innerVector.ReturnToPool(true);
|
||||
this.innerVector = default!;
|
||||
|
||||
ObjectPool.Return(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,31 +14,62 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System.Threading;
|
||||
|
||||
namespace FlatSharp.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// A base flat buffer vector, common to standard vectors and unions.
|
||||
/// </summary>
|
||||
public sealed class FlatBufferVectorBase<T, TInputBuffer, TItemAccessor>
|
||||
: IList<T>, IReadOnlyList<T>, IFlatBufferDeserializedVector
|
||||
: IList<T>
|
||||
, IReadOnlyList<T>
|
||||
, IFlatBufferDeserializedVector
|
||||
, IPoolableObject
|
||||
|
||||
where TInputBuffer : IInputBuffer
|
||||
where TItemAccessor : IVectorItemAccessor<T, TInputBuffer>
|
||||
{
|
||||
private readonly TInputBuffer memory;
|
||||
private readonly TableFieldContext fieldContext;
|
||||
private readonly short remainingDepth;
|
||||
private readonly TItemAccessor itemAccessor;
|
||||
private TInputBuffer memory;
|
||||
private TableFieldContext fieldContext;
|
||||
private short remainingDepth;
|
||||
private TItemAccessor itemAccessor;
|
||||
|
||||
public FlatBufferVectorBase(
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
private FlatBufferVectorBase()
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
{
|
||||
}
|
||||
|
||||
private void Initialize(
|
||||
TInputBuffer memory,
|
||||
TItemAccessor itemAccessor,
|
||||
short remainingDepth,
|
||||
TableFieldContext fieldContext)
|
||||
TableFieldContext fieldContext,
|
||||
FlatBufferDeserializationOption option)
|
||||
{
|
||||
this.memory = memory;
|
||||
this.remainingDepth = remainingDepth;
|
||||
this.fieldContext = fieldContext;
|
||||
this.itemAccessor = itemAccessor;
|
||||
this.DeserializationOption = option;
|
||||
this.fieldContext = fieldContext;
|
||||
}
|
||||
|
||||
public static FlatBufferVectorBase<T, TInputBuffer, TItemAccessor> GetOrCreate(
|
||||
TInputBuffer memory,
|
||||
TItemAccessor itemAccessor,
|
||||
short remainingDepth,
|
||||
TableFieldContext fieldContext,
|
||||
FlatBufferDeserializationOption option)
|
||||
{
|
||||
if (!ObjectPool.TryGet<FlatBufferVectorBase<T, TInputBuffer, TItemAccessor>>(out var item))
|
||||
{
|
||||
item = new FlatBufferVectorBase<T, TInputBuffer, TItemAccessor>();
|
||||
}
|
||||
|
||||
item.Initialize(memory, itemAccessor, remainingDepth, fieldContext, option);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -59,6 +90,8 @@ public sealed class FlatBufferVectorBase<T, TInputBuffer, TItemAccessor>
|
|||
}
|
||||
}
|
||||
|
||||
public FlatBufferDeserializationOption DeserializationOption { get; private set; }
|
||||
|
||||
public int Count => this.itemAccessor.Count;
|
||||
|
||||
public bool IsReadOnly => true;
|
||||
|
@ -153,42 +186,6 @@ public sealed class FlatBufferVectorBase<T, TInputBuffer, TItemAccessor>
|
|||
return -1;
|
||||
}
|
||||
|
||||
public List<T> FlatBufferVectorToList()
|
||||
{
|
||||
int count = this.Count;
|
||||
var list = new List<T>(count);
|
||||
|
||||
var context = this.fieldContext;
|
||||
var remainingDepth = this.remainingDepth;
|
||||
var buffer = this.memory;
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
this.itemAccessor.ParseItem(i, buffer, remainingDepth, context, out T item);
|
||||
list.Add(item);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public ImmutableList<T> ToImmutableList()
|
||||
{
|
||||
int count = this.Count;
|
||||
var list = new T[count];
|
||||
|
||||
var context = this.fieldContext;
|
||||
var remainingDepth = this.remainingDepth;
|
||||
var buffer = this.memory;
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
this.itemAccessor.ParseItem(i, buffer, remainingDepth, context, out T item);
|
||||
list[i] = item;
|
||||
}
|
||||
|
||||
return new ImmutableList<T>(list);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void CheckIndex(int index)
|
||||
{
|
||||
|
@ -232,4 +229,19 @@ public sealed class FlatBufferVectorBase<T, TInputBuffer, TItemAccessor>
|
|||
int IFlatBufferDeserializedVector.OffsetOf(int index) => this.itemAccessor.OffsetOf(index);
|
||||
|
||||
object IFlatBufferDeserializedVector.ItemAt(int index) => this[index]!;
|
||||
|
||||
public void ReturnToPool(bool force = false)
|
||||
{
|
||||
if (this.DeserializationOption.ShouldReturnToPool(force))
|
||||
{
|
||||
var context = Interlocked.Exchange(ref this.fieldContext!, null);
|
||||
if (context is not null)
|
||||
{
|
||||
this.memory = default!;
|
||||
this.remainingDepth = default;
|
||||
this.itemAccessor = default!;
|
||||
ObjectPool.Return(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* Copyright 2020 James Courtney
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
using System.Threading;
|
||||
|
||||
namespace FlatSharp.Internal;
|
||||
|
||||
public sealed class GreedyIndexedVector<TKey, TValue> : IIndexedVector<TKey, TValue>
|
||||
where TValue : class, ISortableTable<TKey>
|
||||
where TKey : notnull
|
||||
{
|
||||
private int alive;
|
||||
private readonly Dictionary<TKey, TValue> backingDictionary;
|
||||
private bool mutable;
|
||||
|
||||
private GreedyIndexedVector()
|
||||
{
|
||||
this.backingDictionary = new Dictionary<TKey, TValue>();
|
||||
this.mutable = true;
|
||||
}
|
||||
|
||||
public static GreedyIndexedVector<TKey, TValue> GetOrCreate<TInputBuffer, TItemAccessor>(
|
||||
FlatBufferVectorBase<TValue, TInputBuffer, TItemAccessor> backing,
|
||||
bool mutable)
|
||||
where TInputBuffer : IInputBuffer
|
||||
where TItemAccessor : IVectorItemAccessor<TValue, TInputBuffer>
|
||||
{
|
||||
if (!ObjectPool.TryGet(out GreedyIndexedVector<TKey, TValue>? vector))
|
||||
{
|
||||
vector = new();
|
||||
}
|
||||
|
||||
vector.mutable = mutable;
|
||||
vector.alive = 1;
|
||||
|
||||
var dict = vector.backingDictionary;
|
||||
|
||||
#if !NETSTANDARD2_0
|
||||
dict.EnsureCapacity(backing.Count);
|
||||
#endif
|
||||
|
||||
foreach (TValue value in backing)
|
||||
{
|
||||
TKey key = SortedVectorHelpers.KeyLookup<TValue, TKey>.KeyGetter(value);
|
||||
if (dict.TryGetValue(key, out var existingValue))
|
||||
{
|
||||
(existingValue as IPoolableObject)?.ReturnToPool();
|
||||
}
|
||||
|
||||
dict[key] = value;
|
||||
}
|
||||
|
||||
// we don't need "backing" any longer
|
||||
backing.ReturnToPool(true);
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the key from the given value.
|
||||
/// </summary>
|
||||
public static TKey GetKey(TValue value) => SortedVectorHelpers.KeyLookup<TValue, TKey>.KeyGetter(value);
|
||||
|
||||
/// <summary>
|
||||
/// An indexer for getting values by their keys.
|
||||
/// </summary>
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get => this.backingDictionary[key];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if this IndexedVector is read only.
|
||||
/// </summary>
|
||||
public bool IsReadOnly => !this.mutable;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the count of items.
|
||||
/// </summary>
|
||||
public int Count => this.backingDictionary.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Freezes an Indexed vector, preventing further modifications.
|
||||
/// </summary>
|
||||
public void Freeze()
|
||||
{
|
||||
this.mutable = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the vector contains the given key.
|
||||
/// </summary>
|
||||
public bool ContainsKey(TKey key) => this.backingDictionary.ContainsKey(key);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the given value from the backing dictionary.
|
||||
/// </summary>
|
||||
public bool TryGetValue(TKey key, [NotNullWhen(true)] out TValue? value)
|
||||
{
|
||||
return this.backingDictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dictionary's enumerator.
|
||||
/// </summary>
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => this.backingDictionary.GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a non-generic enumerator.
|
||||
/// </summary>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Adds or replaces the item with the given key to the indexed vector.
|
||||
/// </summary>
|
||||
public void AddOrReplace(TValue value)
|
||||
{
|
||||
if (!this.mutable)
|
||||
{
|
||||
throw new NotMutableException();
|
||||
}
|
||||
|
||||
this.backingDictionary[GetKey(value)] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to add the value to the indexed vector, if a key does not already exist.
|
||||
/// </summary>
|
||||
public bool Add(TValue value)
|
||||
{
|
||||
if (!this.mutable)
|
||||
{
|
||||
throw new NotMutableException();
|
||||
}
|
||||
|
||||
TKey key = GetKey(value);
|
||||
|
||||
#if NETSTANDARD2_0
|
||||
var dictionary = this.backingDictionary;
|
||||
if (dictionary.ContainsKey(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
dictionary[key] = value;
|
||||
return true;
|
||||
#else
|
||||
return this.backingDictionary.TryAdd(key, value);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (!this.mutable)
|
||||
{
|
||||
throw new NotMutableException();
|
||||
}
|
||||
|
||||
this.backingDictionary.Clear();
|
||||
}
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
if (!this.mutable)
|
||||
{
|
||||
throw new NotMutableException();
|
||||
}
|
||||
|
||||
return this.backingDictionary.Remove(key);
|
||||
}
|
||||
|
||||
public void ReturnToPool(bool unsafeForce = false)
|
||||
{
|
||||
if (FlatBufferDeserializationOption.Greedy.ShouldReturnToPool(unsafeForce))
|
||||
{
|
||||
if (Interlocked.Exchange(ref this.alive, 0) != 0)
|
||||
{
|
||||
var dict = this.backingDictionary;
|
||||
|
||||
foreach (var item in dict)
|
||||
{
|
||||
if (item.Value is IPoolableObject obj)
|
||||
{
|
||||
obj.ReturnToPool(true);
|
||||
}
|
||||
}
|
||||
|
||||
dict.Clear();
|
||||
this.mutable = false;
|
||||
|
||||
ObjectPool.Return(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ namespace FlatSharp;
|
|||
/// <summary>
|
||||
/// An indexed vector -- suitable for accessing values by their keys.
|
||||
/// </summary>
|
||||
public interface IIndexedVector<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
|
||||
public interface IIndexedVector<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>, IPoolableObject
|
||||
where TValue : class
|
||||
where TKey : notnull
|
||||
{
|
||||
|
|
|
@ -14,6 +14,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System.Buffers;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace FlatSharp.Internal;
|
||||
|
||||
/// <summary>
|
||||
|
@ -26,13 +30,49 @@ namespace FlatSharp.Internal;
|
|||
/// - Second, it does not reference <see cref="IList{T}"/> internally, which means it is able to skip a level of virtual indirection
|
||||
/// by using <see cref="T[]"/> directly.
|
||||
/// </remarks>
|
||||
public sealed class ImmutableList<T> : IList<T>, IReadOnlyList<T>
|
||||
public sealed class ImmutableList<T> : IList<T>, IReadOnlyList<T>, IPoolableObject
|
||||
{
|
||||
private readonly T[] list;
|
||||
private List<T> list;
|
||||
private int isAlive;
|
||||
|
||||
public ImmutableList(T[] list)
|
||||
public ImmutableList(IList<T> template)
|
||||
{
|
||||
this.list = list;
|
||||
this.list = template.ToList();
|
||||
}
|
||||
|
||||
public ImmutableList(int capacity)
|
||||
{
|
||||
this.list = new List<T>(capacity);
|
||||
}
|
||||
|
||||
public static ImmutableList<T> GetOrCreate<TInputBuffer, TItemAccessor>(FlatBufferVectorBase<T, TInputBuffer, TItemAccessor> vector)
|
||||
where TInputBuffer : IInputBuffer
|
||||
where TItemAccessor : IVectorItemAccessor<T, TInputBuffer>
|
||||
{
|
||||
int count = vector.Count;
|
||||
|
||||
if (ObjectPool.TryGet(out ImmutableList<T>? list))
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
list.list.EnsureCapacity(count);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
list = new(count);
|
||||
}
|
||||
|
||||
list.isAlive = 1;
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
list.list.Add(vector[i]);
|
||||
}
|
||||
|
||||
// We've copied our stuff -- send the base vector back to where it came from!
|
||||
vector.ReturnToPool(true);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
|
@ -41,7 +81,7 @@ public sealed class ImmutableList<T> : IList<T>, IReadOnlyList<T>
|
|||
set => throw new NotMutableException();
|
||||
}
|
||||
|
||||
public int Count => this.list.Length;
|
||||
public int Count => this.list.Count;
|
||||
|
||||
public bool IsReadOnly => true;
|
||||
|
||||
|
@ -55,25 +95,16 @@ public sealed class ImmutableList<T> : IList<T>, IReadOnlyList<T>
|
|||
throw new NotMutableException();
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return Array.IndexOf(this.list, item) >= 0;
|
||||
}
|
||||
public bool Contains(T item) => this.list.Contains(item);
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
this.list.CopyTo(array, arrayIndex);
|
||||
}
|
||||
public void CopyTo(T[] array, int arrayIndex) => this.list.CopyTo(array, arrayIndex);
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return ((IList<T>)this.list).GetEnumerator();
|
||||
return this.list.GetEnumerator();
|
||||
}
|
||||
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
return Array.IndexOf(this.list, item);
|
||||
}
|
||||
public int IndexOf(T item) => this.list.IndexOf(item);
|
||||
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
|
@ -90,6 +121,29 @@ public sealed class ImmutableList<T> : IList<T>, IReadOnlyList<T>
|
|||
throw new NotMutableException();
|
||||
}
|
||||
|
||||
public void ReturnToPool(bool force)
|
||||
{
|
||||
if (force)
|
||||
{
|
||||
if (Interlocked.Exchange(ref this.isAlive, 0) != 0)
|
||||
{
|
||||
if (!typeof(T).IsValueType)
|
||||
{
|
||||
foreach (var item in this.list)
|
||||
{
|
||||
if (item is IPoolableObject poolable)
|
||||
{
|
||||
poolable.ReturnToPool(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.list.Clear();
|
||||
ObjectPool.Return(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
|
|
|
@ -166,4 +166,9 @@ public sealed class IndexedVector<TKey, TValue> : IIndexedVector<TKey, TValue>
|
|||
|
||||
return this.backingDictionary.Remove(key);
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public void ReturnToPool(bool unsafeForce = false)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright 2022 James Courtney
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace FlatSharp.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// A wrapper around a List{T}.
|
||||
/// </summary>
|
||||
public sealed class PoolableList<T> : IList<T>, IReadOnlyList<T>, IPoolableObject
|
||||
{
|
||||
private List<T> list;
|
||||
private int isAlive;
|
||||
|
||||
public PoolableList(IList<T> template)
|
||||
{
|
||||
this.list = template.ToList();
|
||||
}
|
||||
|
||||
public PoolableList(int capacity)
|
||||
{
|
||||
this.list = new(capacity);
|
||||
}
|
||||
|
||||
public static PoolableList<T> GetOrCreate<TInputBuffer, TItemAccessor>(FlatBufferVectorBase<T, TInputBuffer, TItemAccessor> item)
|
||||
where TInputBuffer : IInputBuffer
|
||||
where TItemAccessor : IVectorItemAccessor<T, TInputBuffer>
|
||||
{
|
||||
int count = item.Count;
|
||||
|
||||
if (ObjectPool.TryGet(out PoolableList<T>? list))
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
list.list.EnsureCapacity(count);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
list = new(count);
|
||||
}
|
||||
|
||||
list.isAlive = 1;
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
list.Add(item[i]);
|
||||
}
|
||||
|
||||
// We've copied our stuff -- send the base vector back to where it came from!
|
||||
item.ReturnToPool(true);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => this.list[index];
|
||||
set => this.list[index] = value;
|
||||
}
|
||||
|
||||
public int Count => this.list.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public void Add(T item) => this.list.Add(item);
|
||||
|
||||
public void Clear() => this.list.Clear();
|
||||
|
||||
public bool Contains(T item) => this.list.Contains(item);
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex) => this.list.CopyTo(array, arrayIndex);
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => this.list.GetEnumerator();
|
||||
|
||||
public int IndexOf(T item) => this.list.IndexOf(item);
|
||||
|
||||
public void Insert(int index, T item) => this.list.Insert(index, item);
|
||||
|
||||
public bool Remove(T item) => this.list.Remove(item);
|
||||
|
||||
public void RemoveAt(int index) => this.list.RemoveAt(index);
|
||||
|
||||
public void ReturnToPool(bool force)
|
||||
{
|
||||
if (force)
|
||||
{
|
||||
var isAlive = Interlocked.Exchange(ref this.isAlive, 0);
|
||||
if (isAlive != 0)
|
||||
{
|
||||
if (!typeof(T).IsValueType)
|
||||
{
|
||||
foreach (var item in this.list)
|
||||
{
|
||||
if (item is IPoolableObject poolable)
|
||||
{
|
||||
poolable.ReturnToPool(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.list.Clear();
|
||||
ObjectPool.Return(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
}
|
|
@ -33,6 +33,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microbench.Current", "Bench
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microbench.6.3.3", "Benchmarks\MicroBench.6.3.3\Microbench.6.3.3.csproj", "{407BD242-0902-4850-9F53-17DF48F3DD6F}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FlatSharpPoolableEndToEndTests", "Tests\FlatSharpPoolableEndToEndTests\FlatSharpPoolableEndToEndTests.csproj", "{7E55A248-4BBD-48AD-B27D-A7E0E705DC89}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -87,6 +89,10 @@ Global
|
|||
{407BD242-0902-4850-9F53-17DF48F3DD6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{407BD242-0902-4850-9F53-17DF48F3DD6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{407BD242-0902-4850-9F53-17DF48F3DD6F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7E55A248-4BBD-48AD-B27D-A7E0E705DC89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7E55A248-4BBD-48AD-B27D-A7E0E705DC89}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7E55A248-4BBD-48AD-B27D-A7E0E705DC89}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7E55A248-4BBD-48AD-B27D-A7E0E705DC89}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -101,6 +107,7 @@ Global
|
|||
{927A2D53-B127-4297-B17A-4E4EEE7ACD0D} = {83478353-8C5A-41C2-84C2-F79488B43CB0}
|
||||
{5A173CC8-53B4-445B-9AA2-68A61A4BE449} = {927A2D53-B127-4297-B17A-4E4EEE7ACD0D}
|
||||
{407BD242-0902-4850-9F53-17DF48F3DD6F} = {927A2D53-B127-4297-B17A-4E4EEE7ACD0D}
|
||||
{7E55A248-4BBD-48AD-B27D-A7E0E705DC89} = {D1E90BAE-FC51-44DB-8215-1D9BB6059886}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {726A4C0E-5760-4B35-8C73-9AC21B2E4C8B}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
|
||||
using FlatSharp.TypeModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace FlatSharp.CodeGen;
|
||||
|
||||
|
@ -24,6 +25,9 @@ internal class DeserializeClassDefinition
|
|||
protected const string OffsetVariableName = "__offset";
|
||||
protected const string VTableVariableName = "__vtable";
|
||||
protected const string RemainingDepthVariableName = "__remainingDepth";
|
||||
protected const string IsAliveVariableName = "__alive";
|
||||
protected const string IsRootVariableName = "__isRoot";
|
||||
protected const string CtorContextVariableName = "__CtorContext";
|
||||
|
||||
protected readonly ITypeModel typeModel;
|
||||
protected readonly FlatBufferSerializerOptions options;
|
||||
|
@ -31,6 +35,7 @@ internal class DeserializeClassDefinition
|
|||
protected readonly List<string> propertyOverrides = new();
|
||||
protected readonly List<string> initializeStatements = new();
|
||||
protected readonly List<string> readMethods = new();
|
||||
protected readonly List<ItemMemberModel> itemModels = new();
|
||||
|
||||
// Maps field name -> field initializer.
|
||||
protected readonly Dictionary<string, string> instanceFieldDefinitions = new();
|
||||
|
@ -54,6 +59,13 @@ internal class DeserializeClassDefinition
|
|||
this.onDeserializeMethod = onDeserializeMethod;
|
||||
|
||||
this.vtableAccessor = "default";
|
||||
this.instanceFieldDefinitions[IsRootVariableName] = $"private bool {IsRootVariableName};";
|
||||
|
||||
if (typeof(IPoolableObject).IsAssignableFrom(this.typeModel.ClrType))
|
||||
{
|
||||
this.instanceFieldDefinitions[IsAliveVariableName] = $"private int {IsAliveVariableName};";
|
||||
this.initializeStatements.Add($"this.{IsAliveVariableName} = 1;");
|
||||
}
|
||||
|
||||
if (this.options.GreedyDeserialize)
|
||||
{
|
||||
|
@ -107,6 +119,8 @@ internal class DeserializeClassDefinition
|
|||
ItemMemberModel itemModel,
|
||||
ParserCodeGenContext context)
|
||||
{
|
||||
this.itemModels.Add(itemModel);
|
||||
|
||||
this.AddFieldDefinitions(itemModel);
|
||||
this.AddPropertyDefinitions(itemModel);
|
||||
this.AddCtorStatements(itemModel);
|
||||
|
@ -260,13 +274,13 @@ internal class DeserializeClassDefinition
|
|||
string onDeserializedStatement = string.Empty;
|
||||
if (this.onDeserializeMethod is not null)
|
||||
{
|
||||
onDeserializedStatement = $"base.{this.onDeserializeMethod.Name}(__CtorContext);";
|
||||
onDeserializedStatement = $"base.{this.onDeserializeMethod.Name}({CtorContextVariableName});";
|
||||
}
|
||||
|
||||
string baseParams = string.Empty;
|
||||
if (ctor.GetParameters().Length != 0)
|
||||
{
|
||||
baseParams = "__CtorContext";
|
||||
baseParams = CtorContextVariableName;
|
||||
}
|
||||
|
||||
string interfaceGlobalName = typeof(IFlatBufferDeserializedObject).GetGlobalCompilableTypeName();
|
||||
|
@ -276,9 +290,12 @@ internal class DeserializeClassDefinition
|
|||
private sealed class {this.ClassName}<TInputBuffer>
|
||||
: {typeModel.GetGlobalCompilableTypeName()}
|
||||
, {interfaceGlobalName}
|
||||
, {typeof(IPoolableObject).GetGlobalCompilableTypeName()}
|
||||
, {typeof(IPoolableObjectDebug).GetGlobalCompilableTypeName()}
|
||||
|
||||
where TInputBuffer : IInputBuffer
|
||||
{{
|
||||
private static readonly {typeof(FlatBufferDeserializationContext).GetGlobalCompilableTypeName()} __CtorContext
|
||||
private static readonly {typeof(FlatBufferDeserializationContext).GetGlobalCompilableTypeName()} {CtorContextVariableName}
|
||||
= new {typeof(FlatBufferDeserializationContext).GetGlobalCompilableTypeName()}({typeof(FlatBufferDeserializationOption).GetGlobalCompilableTypeName()}.{options.DeserializationOption});
|
||||
|
||||
{string.Join("\r\n", this.staticFieldDefinitions.Values)}
|
||||
|
@ -293,11 +310,20 @@ internal class DeserializeClassDefinition
|
|||
|
||||
{this.GetCtorMethodDefinition(onDeserializedStatement, baseParams)}
|
||||
|
||||
{this.GetDisposeMethodBody()}
|
||||
|
||||
{typeof(Type).GetGlobalCompilableTypeName()} {interfaceGlobalName}.{nameof(IFlatBufferDeserializedObject.TableOrStructType)} => typeof({typeModel.GetCompilableTypeName()});
|
||||
{typeof(FlatBufferDeserializationContext).GetGlobalCompilableTypeName()} {interfaceGlobalName}.{nameof(IFlatBufferDeserializedObject.DeserializationContext)} => __CtorContext;
|
||||
{typeof(IInputBuffer).GetGlobalCompilableTypeName()}? {interfaceGlobalName}.{nameof(IFlatBufferDeserializedObject.InputBuffer)} => {this.GetBufferReference()};
|
||||
|
||||
bool {interfaceGlobalName}.{nameof(IFlatBufferDeserializedObject.CanSerializeWithMemoryCopy)} => {this.options.CanSerializeWithMemoryCopy.ToString().ToLowerInvariant()};
|
||||
|
||||
bool {typeof(IPoolableObjectDebug).GetGlobalCompilableTypeName()}.IsRoot
|
||||
{{
|
||||
get => this.{IsRootVariableName};
|
||||
set => this.{IsRootVariableName} = value;
|
||||
}}
|
||||
|
||||
{string.Join("\r\n", this.propertyOverrides)}
|
||||
{string.Join("\r\n", this.readMethods)}
|
||||
}}
|
||||
|
@ -368,10 +394,28 @@ internal class DeserializeClassDefinition
|
|||
|
||||
protected virtual string GetGetOrCreateMethodBody()
|
||||
{
|
||||
return $@"
|
||||
var item = new {this.ClassName}<TInputBuffer>(buffer, offset, remainingDepth);
|
||||
return item;
|
||||
";
|
||||
if (typeof(IPoolableObject).IsAssignableFrom(this.typeModel.ClrType))
|
||||
{
|
||||
return $@"
|
||||
{this.ClassName}<TInputBuffer>? item;
|
||||
|
||||
if (!{typeof(ObjectPool).GetGlobalCompilableTypeName()}.TryGet<{this.ClassName}<TInputBuffer>>(out item))
|
||||
{{
|
||||
item = new {this.ClassName}<TInputBuffer>();
|
||||
}}
|
||||
|
||||
item.Initialize(buffer, offset, remainingDepth);
|
||||
return item;
|
||||
";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $@"
|
||||
{this.ClassName}<TInputBuffer>? item = new();
|
||||
item.Initialize(buffer, offset, remainingDepth);
|
||||
return item;
|
||||
";
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual string GetCtorMethodDefinition(string onDeserializedStatement, string baseCtorParams)
|
||||
|
@ -382,12 +426,113 @@ internal class DeserializeClassDefinition
|
|||
[System.Diagnostics.CodeAnalysis.SetsRequiredMembers]
|
||||
#endif
|
||||
[{typeof(MethodImplAttribute).GetGlobalCompilableTypeName()}({typeof(MethodImplOptions).GetGlobalCompilableTypeName()}.AggressiveInlining)]
|
||||
private {this.ClassName}(TInputBuffer buffer, int offset, short remainingDepth) : base({baseCtorParams})
|
||||
{{
|
||||
private {this.ClassName}() : base({baseCtorParams})
|
||||
{{
|
||||
}}
|
||||
#pragma warning restore CS8618
|
||||
|
||||
[{typeof(MethodImplAttribute).GetGlobalCompilableTypeName()}({typeof(MethodImplOptions).GetGlobalCompilableTypeName()}.AggressiveInlining)]
|
||||
private void Initialize(TInputBuffer buffer, int offset, short remainingDepth)
|
||||
{{
|
||||
{string.Join("\r\n", this.initializeStatements)}
|
||||
{onDeserializedStatement}
|
||||
}}
|
||||
#pragma warning restore CS8618
|
||||
";
|
||||
}
|
||||
|
||||
private string GetDisposeMethodBody()
|
||||
{
|
||||
if (!typeof(IPoolableObject).IsAssignableFrom(this.typeModel.ClrType))
|
||||
{
|
||||
// not disposable.
|
||||
return $@"public void ReturnToPool(bool unsafeForce = false) {{ }}";
|
||||
}
|
||||
|
||||
// Lazy doesn't have to deal with this stuff.
|
||||
IEnumerable<string> disposeFields = Array.Empty<string>();
|
||||
if (!this.options.Lazy)
|
||||
{
|
||||
disposeFields = this.itemModels
|
||||
.Where(
|
||||
x => !x.ItemTypeModel.ClrType.IsValueType)
|
||||
.Select(x => (model: x, isPoolable: typeof(IPoolableObject).IsAssignableFrom(x.ItemTypeModel.ClrType), isInterface: x.ItemTypeModel.ClrType.IsInterface))
|
||||
.Where(x => x.isPoolable || x.isInterface)
|
||||
.Select(x =>
|
||||
{
|
||||
if (x.isPoolable)
|
||||
{
|
||||
return $@"
|
||||
{{
|
||||
var item = this.{GetFieldName(x.model)};
|
||||
this.{GetFieldName(x.model)} = null!;
|
||||
item?.ReturnToPool(true);
|
||||
}}
|
||||
";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $@"
|
||||
{{
|
||||
var item = this.{GetFieldName(x.model)};
|
||||
this.{GetFieldName(x.model)} = null!;
|
||||
(item as IPoolableObject)?.ReturnToPool(true);
|
||||
}}
|
||||
";
|
||||
}
|
||||
});
|
||||
|
||||
// reset all fields to default.
|
||||
disposeFields = disposeFields.Concat(this.itemModels
|
||||
.Select(x => $"this.{GetFieldName(x)} = default({x.ItemTypeModel.GetGlobalCompilableTypeName()})!;"));
|
||||
|
||||
// Reset all masks to default as well.
|
||||
if (!this.options.GreedyDeserialize)
|
||||
{
|
||||
HashSet<string> masks = new HashSet<string>(this.itemModels.Select(GetHasValueFieldName));
|
||||
|
||||
disposeFields = disposeFields.Concat(masks
|
||||
.Select(x => $"this.{x} = 0;"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.options.GreedyDeserialize)
|
||||
{
|
||||
disposeFields = disposeFields.Concat(new[]
|
||||
{
|
||||
$"this.{InputBufferVariableName} = default(TInputBuffer)!;",
|
||||
$"this.{RemainingDepthVariableName} = -1;",
|
||||
$"this.{OffsetVariableName} = -1;",
|
||||
});
|
||||
|
||||
if (this.typeModel.SchemaType == FlatBufferSchemaType.Table)
|
||||
{
|
||||
disposeFields = disposeFields.Concat(new[] { $"this.{VTableVariableName} = default({this.vtableTypeName});" });
|
||||
}
|
||||
}
|
||||
|
||||
string fromRootCondition = $"if (unsafeForce || this.{IsRootVariableName})";
|
||||
if (this.options.Lazy)
|
||||
{
|
||||
fromRootCondition = string.Empty;
|
||||
}
|
||||
|
||||
return $@"
|
||||
|
||||
public override void ReturnToPool(bool unsafeForce = false)
|
||||
{{
|
||||
{{
|
||||
{fromRootCondition}
|
||||
{{
|
||||
if (System.Threading.Interlocked.Exchange(ref this.{IsAliveVariableName}, 0) != 0)
|
||||
{{
|
||||
{string.Join("\r\n", disposeFields)}
|
||||
|
||||
this.{IsRootVariableName} = false;
|
||||
{typeof(ObjectPool).GetGlobalCompilableTypeName()}.Return(this);
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
";
|
||||
}
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ public interface ITypeModel
|
|||
|
||||
/// <summary>
|
||||
/// Indicates the constructor that subclasses should use. This constructor must have either 0 parameters or 1 parameter
|
||||
/// that accepts an instance of <see cref="FlatSharpDeserializationContext"/>.
|
||||
/// that accepts an instance of <see cref="FlatSharp.FlatBufferDeserializationContext"/>.
|
||||
/// </summary>
|
||||
ConstructorInfo? PreferredSubclassConstructor { get; }
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace FlatSharp.TypeModel;
|
||||
|
||||
|
@ -139,6 +140,9 @@ $@"
|
|||
public override CodeGeneratedMethod CreateParseMethodBody(ParserCodeGenContext context)
|
||||
{
|
||||
List<string> switchCases = new List<string>();
|
||||
|
||||
(string? extraClass, string createNew) = GetUnionHelperClass(context);
|
||||
|
||||
for (int i = 0; i < this.UnionElementTypeModel.Length; ++i)
|
||||
{
|
||||
var unionMember = this.UnionElementTypeModel[i];
|
||||
|
@ -160,7 +164,7 @@ $@"
|
|||
$@"
|
||||
case {unionIndex}:
|
||||
{inlineAdjustment}
|
||||
return new {this.GetGlobalCompilableTypeName()}({itemContext.GetParseInvocation(unionMember.ClrType)});
|
||||
return {createNew}({itemContext.GetParseInvocation(unionMember.ClrType)});
|
||||
";
|
||||
switchCases.Add(@case);
|
||||
}
|
||||
|
@ -179,7 +183,94 @@ $@"
|
|||
}}
|
||||
";
|
||||
|
||||
return new CodeGeneratedMethod(body);
|
||||
return new CodeGeneratedMethod(body) { ClassDefinition = extraClass };
|
||||
}
|
||||
|
||||
private (string? classDef, string createNewUnion) GetUnionHelperClass(ParserCodeGenContext context)
|
||||
{
|
||||
if (this.ClrType.IsValueType || !typeof(IPoolableObject).IsAssignableFrom(this.ClrType))
|
||||
{
|
||||
// Nothing special for value-type or non-poolable unions.
|
||||
return (null, $"new {this.GetGlobalCompilableTypeName()}");
|
||||
}
|
||||
|
||||
string className = "unionReader_" + Guid.NewGuid().ToString("n");
|
||||
|
||||
List<string> getOrCreates = new();
|
||||
List<string> returnToPoolCases = new();
|
||||
|
||||
for (int i = 0; i < this.UnionElementTypeModel.Length; ++i)
|
||||
{
|
||||
int unionIndex = i + 1; // unions start at 1.
|
||||
string itemType = this.UnionElementTypeModel[i].GetGlobalCompilableTypeName();
|
||||
|
||||
getOrCreates.Add($@"
|
||||
public static {className} GetOrCreate({itemType} value)
|
||||
{{
|
||||
if (!{typeof(ObjectPool).GetGlobalCompilableTypeName()}.{nameof(ObjectPool.TryGet)}<{className}>(out var union))
|
||||
{{
|
||||
union = new {className}();
|
||||
}}
|
||||
|
||||
union.discriminator = {unionIndex};
|
||||
union.Item{unionIndex} = value;
|
||||
union.isAlive = 1;
|
||||
|
||||
return union;
|
||||
}}
|
||||
");
|
||||
|
||||
string recursiveReturn = string.Empty;
|
||||
if (typeof(IPoolableObject).IsAssignableFrom(this.UnionElementTypeModel[i].ClrType))
|
||||
{
|
||||
recursiveReturn = $"this.Item{unionIndex}?.ReturnToPool(true);";
|
||||
}
|
||||
|
||||
returnToPoolCases.Add($@"
|
||||
case {unionIndex}:
|
||||
{{
|
||||
{recursiveReturn}
|
||||
this.Item{unionIndex} = default({itemType})!;
|
||||
}}
|
||||
break;
|
||||
");
|
||||
}
|
||||
|
||||
string returnCondition = string.Empty;
|
||||
if (!context.Options.Lazy)
|
||||
{
|
||||
returnCondition = "if (unsafeForce)";
|
||||
}
|
||||
|
||||
// Reference type unions are much more special!
|
||||
string extraClass = $@"
|
||||
private sealed class {className} : {this.ClrType.GetGlobalCompilableTypeName()}
|
||||
{{
|
||||
private int isAlive;
|
||||
|
||||
{string.Join("\r\n", getOrCreates)}
|
||||
|
||||
public override void ReturnToPool(bool unsafeForce = false)
|
||||
{{
|
||||
{returnCondition}
|
||||
{{
|
||||
int alive = {typeof(Interlocked).GetGlobalCompilableTypeName()}.Exchange(ref this.isAlive, 0);
|
||||
if (alive > 0)
|
||||
{{
|
||||
switch (base.discriminator)
|
||||
{{
|
||||
{string.Join("\r\n", returnToPoolCases)}
|
||||
}}
|
||||
|
||||
base.discriminator = -1;
|
||||
{typeof(ObjectPool).GetGlobalCompilableTypeName()}.{nameof(ObjectPool.Return)}(this);
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
";
|
||||
|
||||
return (extraClass, $"{className}.GetOrCreate");
|
||||
}
|
||||
|
||||
public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeGenContext context)
|
||||
|
|
|
@ -111,29 +111,30 @@ public class IndexedVectorTypeModel : BaseVectorTypeModel
|
|||
string accessorClassName = $"{vectorClassName}<{context.InputBufferTypeName}>";
|
||||
|
||||
string createFlatBufferVector =
|
||||
$@"new FlatBufferVectorBase<{this.ItemTypeModel.GetGlobalCompilableTypeName()}, {context.InputBufferTypeName}, {accessorClassName}> (
|
||||
$@"FlatBufferVectorBase<{this.ItemTypeModel.GetGlobalCompilableTypeName()}, {context.InputBufferTypeName}, {accessorClassName}>.GetOrCreate(
|
||||
{context.InputBufferVariableName},
|
||||
new {accessorClassName}(
|
||||
{context.OffsetVariableName} + {context.InputBufferVariableName}.{nameof(InputBufferExtensions.ReadUOffset)}({context.OffsetVariableName}),
|
||||
{context.InputBufferVariableName}),
|
||||
{context.RemainingDepthVariableName},
|
||||
{context.TableFieldContextVariableName})";
|
||||
{context.TableFieldContextVariableName},
|
||||
{typeof(FlatBufferDeserializationOption).GetGlobalCompilableTypeName()}.{context.Options.DeserializationOption})";
|
||||
|
||||
string mutable = context.Options.GenerateMutableObjects.ToString().ToLowerInvariant();
|
||||
if (context.Options.GreedyDeserialize)
|
||||
{
|
||||
// Eager indexed vector.
|
||||
body = $@"return new IndexedVector<{keyTypeName}, {valueTypeName}>({createFlatBufferVector}, {mutable});";
|
||||
body = $@"return GreedyIndexedVector<{keyTypeName}, {valueTypeName}>.GetOrCreate<{context.InputBufferTypeName}, {accessorClassName}>({createFlatBufferVector}, {mutable});";
|
||||
}
|
||||
else if (context.Options.Lazy)
|
||||
{
|
||||
// Lazy indexed vector.
|
||||
body = $@"return new FlatBufferIndexedVector<{keyTypeName}, {valueTypeName}, {context.InputBufferTypeName}, {accessorClassName}>({createFlatBufferVector});";
|
||||
body = $@"return FlatBufferIndexedVector<{keyTypeName}, {valueTypeName}, {context.InputBufferTypeName}, {accessorClassName}>.GetOrCreate({createFlatBufferVector});";
|
||||
}
|
||||
else
|
||||
{
|
||||
FlatSharpInternal.Assert(context.Options.Progressive, "expecting progressive");
|
||||
body = $@"return new FlatBufferProgressiveIndexedVector<{keyTypeName}, {valueTypeName}, {context.InputBufferTypeName}, {accessorClassName}>({createFlatBufferVector});";
|
||||
body = $@"return FlatBufferProgressiveIndexedVector<{keyTypeName}, {valueTypeName}, {context.InputBufferTypeName}, {accessorClassName}>.GetOrCreate({createFlatBufferVector});";
|
||||
}
|
||||
|
||||
return new CodeGeneratedMethod(body) { IsMethodInline = true, ClassDefinition = vectorClassDef };
|
||||
|
|
|
@ -113,13 +113,14 @@ public class ListVectorTypeModel : BaseVectorTypeModel
|
|||
string accessorClassName = $"{vectorClassName}<{context.InputBufferTypeName}>";
|
||||
|
||||
string createFlatBufferVector =
|
||||
$@"new FlatBufferVectorBase<{this.ItemTypeModel.GetGlobalCompilableTypeName()}, {context.InputBufferTypeName}, {accessorClassName}> (
|
||||
$@"FlatBufferVectorBase<{this.ItemTypeModel.GetGlobalCompilableTypeName()}, {context.InputBufferTypeName}, {accessorClassName}>.GetOrCreate(
|
||||
{context.InputBufferVariableName},
|
||||
new {accessorClassName}(
|
||||
{context.OffsetVariableName} + {context.InputBufferVariableName}.{nameof(InputBufferExtensions.ReadUOffset)}({context.OffsetVariableName}),
|
||||
{context.InputBufferVariableName}),
|
||||
{context.RemainingDepthVariableName},
|
||||
{context.TableFieldContextVariableName})";
|
||||
{context.TableFieldContextVariableName},
|
||||
{typeof(FlatBufferDeserializationOption).GetGlobalCompilableTypeName()}.{context.Options.DeserializationOption})";
|
||||
|
||||
return new CodeGeneratedMethod(CreateParseBody(this.ItemTypeModel, createFlatBufferVector, accessorClassName, context, isEverWriteThrough)) { ClassDefinition = vectorClassDef };
|
||||
}
|
||||
|
@ -136,15 +137,16 @@ public class ListVectorTypeModel : BaseVectorTypeModel
|
|||
if (context.Options.DeserializationOption == FlatBufferDeserializationOption.GreedyMutable && isEverWriteThrough)
|
||||
{
|
||||
string body = $$"""
|
||||
|
||||
var result = {{createFlatBufferVector}};
|
||||
if ({{context.TableFieldContextVariableName}}.{{nameof(TableFieldContext.WriteThrough)}})
|
||||
{
|
||||
// WriteThrough vectors are not mutable in greedymutable mode.
|
||||
return result.ToImmutableList();
|
||||
return ImmutableList<{{itemTypeModel.ClrType.GetGlobalCompilableTypeName()}}>.GetOrCreate(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return result.FlatBufferVectorToList();
|
||||
return PoolableList<{{itemTypeModel.ClrType.GetGlobalCompilableTypeName()}}>.GetOrCreate(result);
|
||||
}
|
||||
""";
|
||||
|
||||
|
@ -152,13 +154,13 @@ public class ListVectorTypeModel : BaseVectorTypeModel
|
|||
}
|
||||
else if (context.Options.GreedyDeserialize)
|
||||
{
|
||||
string transform = "ToImmutableList()";
|
||||
string transform = "ImmutableList";
|
||||
if (context.Options.GenerateMutableObjects)
|
||||
{
|
||||
transform = "FlatBufferVectorToList()";
|
||||
transform = "PoolableList";
|
||||
}
|
||||
|
||||
return $"return ({createFlatBufferVector}).{transform};";
|
||||
return $"return {transform}<{itemTypeModel.ClrType.GetGlobalCompilableTypeName()}>.GetOrCreate({createFlatBufferVector});";
|
||||
}
|
||||
else if (context.Options.Lazy)
|
||||
{
|
||||
|
@ -167,7 +169,7 @@ public class ListVectorTypeModel : BaseVectorTypeModel
|
|||
else
|
||||
{
|
||||
FlatSharpInternal.Assert(context.Options.Progressive, "expecting progressive");
|
||||
return $"return new FlatBufferProgressiveVector<{itemTypeModel.GetGlobalCompilableTypeName()}, {context.InputBufferTypeName}, {itemAccessorTypeName}>({createFlatBufferVector});";
|
||||
return $"return FlatBufferProgressiveVector<{itemTypeModel.GetGlobalCompilableTypeName()}, {context.InputBufferTypeName}, {itemAccessorTypeName}>.GetOrCreate({createFlatBufferVector});";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,14 +37,15 @@ public class ListVectorOfUnionTypeModel : BaseVectorOfUnionTypeModel
|
|||
string itemAccessorTypeName = $"{className}<{context.InputBufferTypeName}>";
|
||||
|
||||
string createFlatBufferVector =
|
||||
$@"new FlatBufferVectorBase<{this.ItemTypeModel.GetGlobalCompilableTypeName()}, {context.InputBufferTypeName}, {itemAccessorTypeName}> (
|
||||
$@"FlatBufferVectorBase<{this.ItemTypeModel.GetGlobalCompilableTypeName()}, {context.InputBufferTypeName}, {itemAccessorTypeName}>.GetOrCreate(
|
||||
{context.InputBufferVariableName},
|
||||
new {itemAccessorTypeName}(
|
||||
{context.InputBufferVariableName},
|
||||
{context.OffsetVariableName}.offset0 + {context.InputBufferVariableName}.{nameof(InputBufferExtensions.ReadUOffset)}({context.OffsetVariableName}.offset0),
|
||||
{context.OffsetVariableName}.offset1 + {context.InputBufferVariableName}.{nameof(InputBufferExtensions.ReadUOffset)}({context.OffsetVariableName}.offset1)),
|
||||
{context.RemainingDepthVariableName},
|
||||
{context.TableFieldContextVariableName})";
|
||||
{context.TableFieldContextVariableName},
|
||||
{typeof(FlatBufferDeserializationOption).GetGlobalCompilableTypeName()}.{context.Options.DeserializationOption})";
|
||||
|
||||
return new CodeGeneratedMethod(ListVectorTypeModel.CreateParseBody(
|
||||
this.ItemTypeModel,
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
using FlatSharp.Internal;
|
||||
|
||||
namespace FlatSharpTests;
|
||||
namespace FlatSharpEndToEndTests.ImmutableList;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the FlatBufferVector class that implements IList.
|
||||
|
|
|
@ -24,12 +24,7 @@ public class IndexedVectorTests
|
|||
private List<string> stringKeys;
|
||||
|
||||
private Container stringVectorSource;
|
||||
private Container stringVectorLazy;
|
||||
private Container stringVectorProgressive;
|
||||
|
||||
private Container intVectorSource;
|
||||
private Container intVectorParsed;
|
||||
private Container intVectorProgressive;
|
||||
|
||||
public IndexedVectorTests()
|
||||
{
|
||||
|
@ -48,60 +43,73 @@ public class IndexedVectorTests
|
|||
|
||||
this.stringVectorSource.StringVector.Freeze();
|
||||
this.intVectorSource.IntVector.Freeze();
|
||||
|
||||
this.stringVectorLazy = stringVectorSource.SerializeAndParse(FlatBufferDeserializationOption.Lazy);
|
||||
this.stringVectorProgressive = stringVectorSource.SerializeAndParse(FlatBufferDeserializationOption.Progressive);
|
||||
|
||||
this.intVectorParsed = intVectorSource.SerializeAndParse(FlatBufferDeserializationOption.Lazy);
|
||||
this.intVectorProgressive = intVectorSource.SerializeAndParse(FlatBufferDeserializationOption.Progressive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexedVector_KeyNotFound()
|
||||
[Theory]
|
||||
[ClassData(typeof(DeserializationOptionClassData))]
|
||||
public void IndexedVector_KeyNotFound(FlatBufferDeserializationOption option)
|
||||
{
|
||||
Container stringValue = this.stringVectorSource.SerializeAndParse(option);
|
||||
Container intValue = this.intVectorSource.SerializeAndParse(option);
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() => this.stringVectorSource.StringVector[null]);
|
||||
Assert.Throws<KeyNotFoundException>(() => this.stringVectorSource.StringVector[string.Empty]);
|
||||
Assert.Throws<ArgumentNullException>(() => this.stringVectorLazy.StringVector[null]);
|
||||
Assert.Throws<KeyNotFoundException>(() => this.stringVectorLazy.StringVector[string.Empty]);
|
||||
Assert.Throws<ArgumentNullException>(() => this.stringVectorProgressive.StringVector[null]);
|
||||
Assert.Throws<KeyNotFoundException>(() => this.stringVectorProgressive.StringVector[string.Empty]);
|
||||
Assert.Throws<ArgumentNullException>(() => stringValue.StringVector[null]);
|
||||
Assert.Throws<KeyNotFoundException>(() => stringValue.StringVector[string.Empty]);
|
||||
|
||||
Assert.Throws<KeyNotFoundException>(() => this.intVectorSource.IntVector[int.MinValue]);
|
||||
Assert.Throws<KeyNotFoundException>(() => this.intVectorSource.IntVector[int.MaxValue]);
|
||||
Assert.Throws<KeyNotFoundException>(() => this.intVectorParsed.IntVector[int.MinValue]);
|
||||
Assert.Throws<KeyNotFoundException>(() => this.intVectorParsed.IntVector[int.MaxValue]);
|
||||
Assert.Throws<KeyNotFoundException>(() => this.intVectorProgressive.IntVector[int.MinValue]);
|
||||
Assert.Throws<KeyNotFoundException>(() => this.intVectorProgressive.IntVector[int.MaxValue]);
|
||||
Assert.Throws<KeyNotFoundException>(() => intValue.IntVector[int.MinValue]);
|
||||
Assert.Throws<KeyNotFoundException>(() => intValue.IntVector[int.MaxValue]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexedVector_NotMutable()
|
||||
[Theory]
|
||||
[ClassData(typeof(DeserializationOptionClassData))]
|
||||
public void IndexedVector_NotMutable(FlatBufferDeserializationOption option)
|
||||
{
|
||||
Assert.True(this.stringVectorLazy.StringVector.IsReadOnly);
|
||||
Container stringValue = this.stringVectorSource.SerializeAndParse(option);
|
||||
|
||||
Assert.Equal(
|
||||
FlatBufferDeserializationOption.GreedyMutable != option,
|
||||
stringValue.StringVector.IsReadOnly);
|
||||
|
||||
Assert.True(this.stringVectorSource.StringVector.IsReadOnly);
|
||||
|
||||
// root is frozen.
|
||||
Assert.Throws<NotMutableException>(() => this.stringVectorSource.StringVector.AddOrReplace(null));
|
||||
Assert.Throws<NotMutableException>(() => this.stringVectorSource.StringVector.Clear());
|
||||
Assert.Throws<NotMutableException>(() => this.stringVectorSource.StringVector.Remove(null));
|
||||
Assert.Throws<NotMutableException>(() => this.stringVectorSource.StringVector.Add(null));
|
||||
|
||||
Assert.Throws<NotMutableException>(() => this.stringVectorLazy.StringVector.AddOrReplace(null));
|
||||
Assert.Throws<NotMutableException>(() => this.stringVectorLazy.StringVector.Clear());
|
||||
Assert.Throws<NotMutableException>(() => this.stringVectorLazy.StringVector.Remove(null));
|
||||
Assert.Throws<NotMutableException>(() => this.stringVectorLazy.StringVector.Add(null));
|
||||
Action[] actions = new Action[]
|
||||
{
|
||||
() => stringValue.StringVector.AddOrReplace(new StringKey { Key = "foo" }),
|
||||
() => stringValue.StringVector.Clear(),
|
||||
() => stringValue.StringVector.Remove("foo"),
|
||||
() => stringValue.StringVector.Add(new StringKey { Key = "foo" }),
|
||||
};
|
||||
|
||||
Assert.Throws<NotMutableException>(() => this.stringVectorProgressive.StringVector.AddOrReplace(null));
|
||||
Assert.Throws<NotMutableException>(() => this.stringVectorProgressive.StringVector.Clear());
|
||||
Assert.Throws<NotMutableException>(() => this.stringVectorProgressive.StringVector.Remove(null));
|
||||
Assert.Throws<NotMutableException>(() => this.stringVectorProgressive.StringVector.Add(null));
|
||||
foreach (var item in actions)
|
||||
{
|
||||
if (option == FlatBufferDeserializationOption.GreedyMutable)
|
||||
{
|
||||
item();
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Throws<NotMutableException>(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexedVector_GetEnumerator()
|
||||
[Theory]
|
||||
[ClassData(typeof(DeserializationOptionClassData))]
|
||||
public void IndexedVector_GetEnumerator(FlatBufferDeserializationOption option)
|
||||
{
|
||||
EnumeratorTest(this.stringVectorLazy.StringVector);
|
||||
Container stringValue = this.stringVectorSource.SerializeAndParse(option);
|
||||
|
||||
EnumeratorTest(stringValue.StringVector);
|
||||
EnumeratorTest(this.stringVectorSource.StringVector);
|
||||
EnumeratorTest(this.stringVectorProgressive.StringVector);
|
||||
}
|
||||
|
||||
private void EnumeratorTest(IIndexedVector<string, StringKey> vector)
|
||||
|
@ -133,37 +141,36 @@ public class IndexedVectorTests
|
|||
Assert.Empty(keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexedVector_ContainsKey()
|
||||
[Theory]
|
||||
[ClassData(typeof(DeserializationOptionClassData))]
|
||||
public void IndexedVector_ContainsKey(FlatBufferDeserializationOption option)
|
||||
{
|
||||
Container stringValue = this.stringVectorSource.SerializeAndParse(option);
|
||||
|
||||
Assert.True(this.stringVectorSource.StringVector.ContainsKey("1"));
|
||||
Assert.True(this.stringVectorSource.StringVector.ContainsKey("5"));
|
||||
Assert.False(this.stringVectorSource.StringVector.ContainsKey("20"));
|
||||
Assert.Throws<ArgumentNullException>(() => this.stringVectorSource.StringVector.ContainsKey(null));
|
||||
|
||||
Assert.True(this.stringVectorLazy.StringVector.ContainsKey("1"));
|
||||
Assert.True(this.stringVectorLazy.StringVector.ContainsKey("5"));
|
||||
Assert.False(this.stringVectorLazy.StringVector.ContainsKey("20"));
|
||||
Assert.Throws<ArgumentNullException>(() => this.stringVectorLazy.StringVector.ContainsKey(null));
|
||||
|
||||
Assert.True(this.stringVectorProgressive.StringVector.ContainsKey("1"));
|
||||
Assert.True(this.stringVectorProgressive.StringVector.ContainsKey("5"));
|
||||
Assert.False(this.stringVectorProgressive.StringVector.ContainsKey("20"));
|
||||
Assert.Throws<ArgumentNullException>(() => this.stringVectorProgressive.StringVector.ContainsKey(null));
|
||||
Assert.True(stringValue.StringVector.ContainsKey("1"));
|
||||
Assert.True(stringValue.StringVector.ContainsKey("5"));
|
||||
Assert.False(stringValue.StringVector.ContainsKey("20"));
|
||||
Assert.Throws<ArgumentNullException>(() => stringValue.StringVector.ContainsKey(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexedVector_Caching()
|
||||
[Theory]
|
||||
[ClassData(typeof(DeserializationOptionClassData))]
|
||||
public void IndexedVector_Caching(FlatBufferDeserializationOption option)
|
||||
{
|
||||
Container stringValue = this.stringVectorSource.SerializeAndParse(option);
|
||||
|
||||
foreach (var key in this.stringKeys)
|
||||
{
|
||||
Assert.True(this.stringVectorLazy.StringVector.TryGetValue(key, out var value));
|
||||
Assert.True(this.stringVectorLazy.StringVector.TryGetValue(key, out var value2));
|
||||
Assert.NotSame(value, value2);
|
||||
|
||||
Assert.True(this.stringVectorProgressive.StringVector.TryGetValue(key, out value));
|
||||
Assert.True(this.stringVectorProgressive.StringVector.TryGetValue(key, out value2));
|
||||
Assert.Same(value, value2);
|
||||
Assert.True(stringValue.StringVector.TryGetValue(key, out var value));
|
||||
Assert.True(stringValue.StringVector.TryGetValue(key, out var value2));
|
||||
Assert.Equal(
|
||||
option != FlatBufferDeserializationOption.Lazy,
|
||||
object.ReferenceEquals(value, value2));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright 2022 James Courtney
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
using FlatSharp.Internal;
|
||||
|
||||
namespace FlatSharpEndToEndTests.PoolableList;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the FlatBufferVector class that implements IList.
|
||||
/// </summary>
|
||||
|
||||
public class PoolableListTests
|
||||
{
|
||||
private PoolableList<int> Items => new PoolableList<int>(new[] { 0, 1, 2, 3, 4, 5, });
|
||||
|
||||
[Fact]
|
||||
public void Clear()
|
||||
{
|
||||
var items = this.Items;
|
||||
Assert.NotEmpty(items);
|
||||
items.Clear();
|
||||
Assert.Empty(items);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveAt()
|
||||
{
|
||||
var items = this.Items;
|
||||
Assert.Equal(2, items[2]);
|
||||
Assert.Equal(6, items.Count);
|
||||
items.RemoveAt(2);
|
||||
Assert.Equal(3, items[2]);
|
||||
Assert.Equal(5, items.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove()
|
||||
{
|
||||
var items = this.Items;
|
||||
Assert.Equal(6, items.Count);
|
||||
items.Remove(4);
|
||||
Assert.Equal(5, items.Count);
|
||||
Assert.Equal(5, items[4]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Setter()
|
||||
{
|
||||
var items = this.Items;
|
||||
Assert.Equal(2, items[2]);
|
||||
items[2] = 10;
|
||||
Assert.Equal(10, items[2]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add()
|
||||
{
|
||||
var items = this.Items;
|
||||
Assert.Equal(6, items.Count);
|
||||
items.Add(6);
|
||||
Assert.Equal(7, items.Count);
|
||||
Assert.Equal(6, items[6]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Insert()
|
||||
{
|
||||
var items = this.Items;
|
||||
Assert.Equal(6, items.Count);
|
||||
items.Insert(1, 10);
|
||||
Assert.Equal(7, items.Count);
|
||||
Assert.Equal(10, items[1]);
|
||||
Assert.Equal(1, items[2]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Get()
|
||||
{
|
||||
var items = this.Items;
|
||||
for (int i = 0; i < items.Count; ++i)
|
||||
{
|
||||
Assert.Equal(i, items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Contains()
|
||||
{
|
||||
var items = this.Items;
|
||||
for (int i = 0; i < items.Count; ++i)
|
||||
{
|
||||
#pragma warning disable xUnit2017 // Do not use Contains() to check if a value exists in a collection
|
||||
// Justification: want to ensure correct method is invoked.
|
||||
|
||||
Assert.True(items.Contains(i));
|
||||
|
||||
#pragma warning restore xUnit2017 // Do not use Contains() to check if a value exists in a collection
|
||||
|
||||
Assert.Equal(i, items.IndexOf(i));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEnumerator()
|
||||
{
|
||||
var items = this.Items;
|
||||
int i = 0;
|
||||
foreach (var item in items)
|
||||
{
|
||||
Assert.Equal(i++, item);
|
||||
}
|
||||
|
||||
Assert.Equal(i, items.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CopyTo()
|
||||
{
|
||||
var items = this.Items;
|
||||
int[] temp = new int[20];
|
||||
items.CopyTo(temp, 10);
|
||||
|
||||
for (int i = 0; i < items.Count; ++i)
|
||||
{
|
||||
Assert.Equal(temp[i + 10], items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadOnly()
|
||||
{
|
||||
var items = this.Items;
|
||||
Assert.False(items.IsReadOnly);
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
using FlatSharp.Internal;
|
||||
|
||||
namespace FlatSharpTests;
|
||||
namespace FlatSharpEndToEndTests.VTables;
|
||||
|
||||
public class VTableTests
|
||||
{
|
||||
|
|
|
@ -19,10 +19,7 @@ namespace FlatSharpEndToEndTests.CopyConstructors;
|
|||
public class CopyConstructorTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(FlatBufferDeserializationOption.Greedy)]
|
||||
[InlineData(FlatBufferDeserializationOption.GreedyMutable)]
|
||||
[InlineData(FlatBufferDeserializationOption.Lazy)]
|
||||
[InlineData(FlatBufferDeserializationOption.Progressive)]
|
||||
[ClassData(typeof(DeserializationOptionClassData))]
|
||||
public void CopyConstructorsTest(FlatBufferDeserializationOption option)
|
||||
{
|
||||
OuterTable original = new OuterTable
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2018 James Courtney
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
namespace FlatSharpEndToEndTests;
|
||||
|
||||
public class DeserializationOptionClassData : IEnumerable<object[]>
|
||||
{
|
||||
public IEnumerator<object[]> GetEnumerator()
|
||||
{
|
||||
yield return new object[] { FlatBufferDeserializationOption.Lazy };
|
||||
yield return new object[] { FlatBufferDeserializationOption.Progressive };
|
||||
yield return new object[] { FlatBufferDeserializationOption.Greedy };
|
||||
yield return new object[] { FlatBufferDeserializationOption.GreedyMutable };
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
|
@ -46,10 +46,7 @@ public class InputBufferTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(FlatBufferDeserializationOption.Greedy)]
|
||||
[InlineData(FlatBufferDeserializationOption.GreedyMutable)]
|
||||
[InlineData(FlatBufferDeserializationOption.Lazy)]
|
||||
[InlineData(FlatBufferDeserializationOption.Progressive)]
|
||||
[ClassData(typeof(DeserializationOptionClassData))]
|
||||
public void SerializationInvocations(FlatBufferDeserializationOption option)
|
||||
{
|
||||
var serializer = PrimitiveTypesTable.Serializer.WithSettings(settings => settings.UseDeserializationMode(option));
|
||||
|
|
|
@ -384,8 +384,7 @@ public partial class OracleDeserializeTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(FlatBufferDeserializationOption.Greedy)]
|
||||
[InlineData(FlatBufferDeserializationOption.Lazy)]
|
||||
[ClassData(typeof(DeserializationOptionClassData))]
|
||||
public void SortedVectors(FlatBufferDeserializationOption option)
|
||||
{
|
||||
var builder = new FlatBufferBuilder(1024 * 1024);
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<PipelineBuild>false</PipelineBuild>
|
||||
<PipelineBuild Condition=" '$(AppVeyorBuild)' == 'true' or '$(CoverageBuild)' == 'true' ">true</PipelineBuild>
|
||||
<TargetFrameworks>net7.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(AppVeyorBuild)' == 'true' ">net472;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(CoverageBuild)' == 'true' ">net7.0</TargetFrameworks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AssemblyName>PoolingTests</AssemblyName>
|
||||
<RootNamespace>FlatSharpTests</RootNamespace>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Nullable>annotations</Nullable>
|
||||
<NoWarn>CS1591</NoWarn>
|
||||
<FlatSharpCompilerPath>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)\..\..\FlatSharp.Compiler\bin\$(Configuration)\net7.0\FlatSharp.Compiler.dll'))</FlatSharpCompilerPath>
|
||||
<FlatSharpNameNormalization>true</FlatSharpNameNormalization>
|
||||
<FlatSharpPoolable>true</FlatSharpPoolable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Grpc.Core" Version="2.46.5" />
|
||||
<PackageReference Include="Grpc.Core.Api" Version="2.49.0" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="6.0.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.utility" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\FlatSharp.Compiler\FlatSharp.Compiler.csproj" Condition=" '$(PipelineBuild)' != 'true' " />
|
||||
<ProjectReference Include="..\..\FlatSharp.Runtime\FlatSharp.Runtime.csproj" />
|
||||
<ProjectReference Include="..\..\FlatSharp\FlatSharp.csproj" Condition=" '$(PipelineBuild)' != 'true' " />
|
||||
<ProjectReference Include="..\..\Google.FlatBuffers\Google.FlatBuffers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FlatSharpSchema Include="**\*.fbs" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\..\FlatSharp.Compiler\FlatSharp.Compiler.targets" Condition=" '$(PipelineBuild)' != 'true' " />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
/*
|
||||
* Copyright 2021 James Courtney
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
global using System;
|
||||
global using System.Buffers;
|
||||
global using System.Buffers.Binary;
|
||||
global using System.Collections;
|
||||
global using System.Collections.Generic;
|
||||
global using System.ComponentModel;
|
||||
global using System.Diagnostics;
|
||||
global using System.Diagnostics.CodeAnalysis;
|
||||
global using System.Linq;
|
||||
global using System.Reflection;
|
||||
global using System.Runtime.CompilerServices;
|
||||
global using FlatSharp;
|
||||
global using FlatSharp.Attributes;
|
||||
global using Xunit;
|
|
@ -0,0 +1,405 @@
|
|||
/*
|
||||
* Copyright 2021 James Courtney
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
namespace FlatSharpEndToEndTests.PoolingTests;
|
||||
|
||||
public class PoolingTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(FlatBufferDeserializationOption.Progressive)]
|
||||
[InlineData(FlatBufferDeserializationOption.Greedy)]
|
||||
[InlineData(FlatBufferDeserializationOption.GreedyMutable)]
|
||||
public void NotLazy_Pools(FlatBufferDeserializationOption option)
|
||||
{
|
||||
#if DEBUG
|
||||
var testPool = new TestObjectPool();
|
||||
ObjectPool.Instance = testPool;
|
||||
#else
|
||||
ObjectPool.MaxToRetain = 100;
|
||||
#endif
|
||||
|
||||
byte[] buffer = CreateRoot(1);
|
||||
|
||||
Root parsed = Root.Serializer.Parse(buffer, option);
|
||||
VerifyRoot(parsed, 1);
|
||||
|
||||
HashSet<object> seenObjects = new();
|
||||
|
||||
seenObjects.Add(parsed.InnerTable);
|
||||
seenObjects.Add(parsed.RefStruct);
|
||||
seenObjects.Add(parsed.VectorOfRefStruct);
|
||||
seenObjects.Add(parsed.VectorOfTable);
|
||||
seenObjects.Add(parsed.VectorOfRefStruct);
|
||||
seenObjects.Add(parsed.VectorOfValueStruct);
|
||||
seenObjects.Add(parsed.VectorOfUnion);
|
||||
seenObjects.Add(parsed.IndexedVector);
|
||||
|
||||
foreach (var item in parsed.VectorOfTable)
|
||||
{
|
||||
seenObjects.Add(item);
|
||||
}
|
||||
|
||||
foreach (var item in parsed.VectorOfRefStruct)
|
||||
{
|
||||
seenObjects.Add(item);
|
||||
}
|
||||
|
||||
foreach (var item in parsed.VectorOfUnion)
|
||||
{
|
||||
seenObjects.Add(item);
|
||||
seenObjects.Add(item.Accept<Visitor, object>(new Visitor()));
|
||||
}
|
||||
|
||||
foreach (var item in parsed.IndexedVector.Select(x => x.Value))
|
||||
{
|
||||
seenObjects.Add(item);
|
||||
}
|
||||
|
||||
// Release all our stuff.
|
||||
parsed.ReturnToPool();
|
||||
|
||||
#if DEBUG
|
||||
foreach (var item in seenObjects)
|
||||
{
|
||||
Assert.True(testPool.IsInPool(item));
|
||||
|
||||
int count = testPool.Count(item);
|
||||
Assert.True(count > 0);
|
||||
|
||||
// return again -- verify that we don't double return.
|
||||
((IPoolableObject)item).ReturnToPool(true);
|
||||
Assert.Equal(count, testPool.Count(item));
|
||||
}
|
||||
#endif
|
||||
|
||||
buffer = CreateRoot(2);
|
||||
|
||||
// Parse again, and ensure that all the things are in the hash set.
|
||||
parsed = Root.Serializer.Parse(buffer, option);
|
||||
VerifyRoot(parsed, 2);
|
||||
|
||||
Assert.Contains(parsed.InnerTable, seenObjects);
|
||||
Assert.Contains(parsed.RefStruct, seenObjects);
|
||||
Assert.Contains(parsed.VectorOfRefStruct, seenObjects);
|
||||
Assert.Contains(parsed.VectorOfTable, seenObjects);
|
||||
Assert.Contains(parsed.VectorOfValueStruct, seenObjects);
|
||||
Assert.Contains(parsed.VectorOfUnion, seenObjects);
|
||||
Assert.Contains(parsed.IndexedVector, seenObjects);
|
||||
|
||||
foreach (var item in parsed.VectorOfTable)
|
||||
{
|
||||
Assert.Contains(item, seenObjects);
|
||||
}
|
||||
|
||||
foreach (var item in parsed.VectorOfRefStruct)
|
||||
{
|
||||
Assert.Contains(item, seenObjects);
|
||||
}
|
||||
|
||||
foreach (var item in parsed.VectorOfUnion)
|
||||
{
|
||||
Assert.Contains(item, seenObjects);
|
||||
Assert.Contains(item.Accept<Visitor, object>(new Visitor()), seenObjects);
|
||||
}
|
||||
|
||||
foreach (var item in parsed.IndexedVector.Select(x => x.Value))
|
||||
{
|
||||
Assert.Contains(item, seenObjects);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
foreach (var item in seenObjects)
|
||||
{
|
||||
Assert.False(testPool.IsInPool(item));
|
||||
Assert.Equal(0, testPool.Count(item));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
[Theory]
|
||||
[InlineData(FlatBufferDeserializationOption.Progressive)]
|
||||
[InlineData(FlatBufferDeserializationOption.Greedy)]
|
||||
[InlineData(FlatBufferDeserializationOption.GreedyMutable)]
|
||||
public void NotLazy_NonRoot_NoOp(FlatBufferDeserializationOption option)
|
||||
{
|
||||
var testPool = new TestObjectPool();
|
||||
ObjectPool.Instance = testPool;
|
||||
|
||||
void AssertNotInPool(object item)
|
||||
{
|
||||
Assert.False(testPool.IsInPool(item));
|
||||
|
||||
IPoolableObject? obj = item as IPoolableObject;
|
||||
|
||||
Assert.NotNull(obj);
|
||||
|
||||
// Verify call is ignored for non-root objects.
|
||||
obj.ReturnToPool();
|
||||
Assert.False(testPool.IsInPool(item));
|
||||
}
|
||||
|
||||
byte[] buffer = CreateRoot(1);
|
||||
|
||||
Root parsed = Root.Serializer.Parse(buffer, option);
|
||||
VerifyRoot(parsed, 1);
|
||||
|
||||
AssertNotInPool(parsed.InnerTable);
|
||||
AssertNotInPool(parsed.RefStruct);
|
||||
AssertNotInPool(parsed.VectorOfRefStruct);
|
||||
AssertNotInPool(parsed.VectorOfValueStruct);
|
||||
AssertNotInPool(parsed.VectorOfTable);
|
||||
AssertNotInPool(parsed.VectorOfUnion);
|
||||
AssertNotInPool(parsed.IndexedVector);
|
||||
|
||||
foreach (var item in parsed.VectorOfRefStruct)
|
||||
{
|
||||
AssertNotInPool(item);
|
||||
}
|
||||
|
||||
foreach (var item in parsed.VectorOfTable)
|
||||
{
|
||||
AssertNotInPool(item);
|
||||
}
|
||||
|
||||
foreach (var item in parsed.VectorOfUnion)
|
||||
{
|
||||
AssertNotInPool(item);
|
||||
AssertNotInPool(item.Accept<Visitor, object>(default));
|
||||
}
|
||||
|
||||
foreach (var value in parsed.IndexedVector.Select(x => x.Value))
|
||||
{
|
||||
AssertNotInPool(value);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Lazy_MultipleReturn()
|
||||
{
|
||||
var testPool = new TestObjectPool();
|
||||
ObjectPool.Instance = testPool;
|
||||
|
||||
byte[] buffer = CreateRoot(1);
|
||||
byte[] buffer2 = CreateRoot(2);
|
||||
|
||||
Root parsedOriginal = Root.Serializer.Parse(buffer, FlatBufferDeserializationOption.Lazy);
|
||||
VerifyRoot(parsedOriginal, 1);
|
||||
|
||||
Assert.False(testPool.IsInPool(parsedOriginal));
|
||||
Assert.Equal(0, testPool.Count(parsedOriginal));
|
||||
|
||||
parsedOriginal.ReturnToPool();
|
||||
|
||||
Assert.True(testPool.IsInPool(parsedOriginal));
|
||||
Assert.Equal(1, testPool.Count(parsedOriginal));
|
||||
|
||||
Root parsed2 = Root.Serializer.Parse(buffer2, FlatBufferDeserializationOption.Lazy);
|
||||
|
||||
Assert.Same(parsedOriginal, parsed2);
|
||||
VerifyRoot(parsed2, 2);
|
||||
VerifyRoot(parsedOriginal, 2);
|
||||
|
||||
Root parsed3 = Root.Serializer.Parse(buffer, FlatBufferDeserializationOption.Lazy);
|
||||
VerifyRoot(parsed3, 1);
|
||||
|
||||
Assert.NotSame(parsedOriginal, parsed3);
|
||||
|
||||
Assert.Equal(0, testPool.Count(parsedOriginal));
|
||||
Assert.False(testPool.IsInPool(parsedOriginal));
|
||||
Assert.False(testPool.IsInPool(parsed2));
|
||||
Assert.False(testPool.IsInPool(parsed3));
|
||||
|
||||
// Return works.
|
||||
parsedOriginal.ReturnToPool();
|
||||
Assert.Equal(1, testPool.Count(parsedOriginal));
|
||||
Assert.True(testPool.IsInPool(parsedOriginal));
|
||||
Assert.True(testPool.IsInPool(parsed2));
|
||||
Assert.False(testPool.IsInPool(parsed3));
|
||||
|
||||
// Won't have any effect.
|
||||
parsed2.ReturnToPool();
|
||||
Assert.Equal(1, testPool.Count(parsedOriginal));
|
||||
Assert.True(testPool.IsInPool(parsedOriginal));
|
||||
Assert.True(testPool.IsInPool(parsed2));
|
||||
Assert.False(testPool.IsInPool(parsed3));
|
||||
|
||||
parsed3.ReturnToPool();
|
||||
Assert.Equal(2, testPool.Count(parsedOriginal));
|
||||
Assert.True(testPool.IsInPool(parsedOriginal));
|
||||
Assert.True(testPool.IsInPool(parsed2));
|
||||
Assert.True(testPool.IsInPool(parsed3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Lazy_Vectors()
|
||||
{
|
||||
var testPool = new TestObjectPool();
|
||||
ObjectPool.Instance = testPool;
|
||||
|
||||
byte[] buffer = CreateRoot(1);
|
||||
|
||||
Root parsedOriginal = Root.Serializer.Parse(buffer, FlatBufferDeserializationOption.Lazy);
|
||||
VerifyRoot(parsedOriginal, 1);
|
||||
|
||||
{
|
||||
var structVector = parsedOriginal.VectorOfRefStruct;
|
||||
|
||||
HashSet<RefStruct> seenInstances = new();
|
||||
|
||||
foreach (RefStruct refStruct in structVector)
|
||||
{
|
||||
seenInstances.Add(refStruct);
|
||||
Assert.False(testPool.IsInPool(refStruct));
|
||||
|
||||
refStruct.ReturnToPool();
|
||||
Assert.True(testPool.IsInPool(refStruct));
|
||||
}
|
||||
|
||||
Assert.Single(seenInstances);
|
||||
}
|
||||
|
||||
{
|
||||
var unionVector = parsedOriginal.VectorOfUnion;
|
||||
HashSet<object> seenObjects = new();
|
||||
|
||||
foreach (var item in unionVector)
|
||||
{
|
||||
seenObjects.Add(item);
|
||||
Assert.False(testPool.IsInPool(item));
|
||||
|
||||
object value = item.Accept<Visitor, object>(default);
|
||||
seenObjects.Add(value);
|
||||
Assert.False(testPool.IsInPool(value));
|
||||
|
||||
// returns the underlying item, even in lazy mode.
|
||||
item.ReturnToPool();
|
||||
|
||||
Assert.True(testPool.IsInPool(item));
|
||||
Assert.True(testPool.IsInPool(value));
|
||||
}
|
||||
|
||||
Assert.Equal(3, seenObjects.Count);
|
||||
}
|
||||
|
||||
{
|
||||
var indexedVector = parsedOriginal.IndexedVector;
|
||||
HashSet<object> seenObjects = new();
|
||||
|
||||
foreach (var item in indexedVector)
|
||||
{
|
||||
KeyValue value = item.Value;
|
||||
|
||||
Assert.False(testPool.IsInPool(value));
|
||||
seenObjects.Add(value);
|
||||
|
||||
// returns the underlying item, even in lazy mode.
|
||||
value.ReturnToPool();
|
||||
Assert.True(testPool.IsInPool(value));
|
||||
}
|
||||
|
||||
indexedVector.ReturnToPool();
|
||||
Assert.True(testPool.IsInPool(indexedVector));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private byte[] CreateRoot(int value)
|
||||
{
|
||||
Root root = new()
|
||||
{
|
||||
InnerTable = new InnerTable() { X = value },
|
||||
RefStruct = new RefStruct() { X = value, },
|
||||
ValueStruct = new ValueStruct() { X = value },
|
||||
VectorOfRefStruct = new[] { new RefStruct() { X = value }, new() { X = value } },
|
||||
VectorOfTable = new[] { new InnerTable() { X = value }, new() { X = value } },
|
||||
VectorOfValueStruct = new[] { new ValueStruct { X = value }, new() { X = value } },
|
||||
VectorOfUnion = new[] { new InnerUnion(new RefStruct { X = value }), new InnerUnion(new InnerTable { X = value })},
|
||||
IndexedVector = new IndexedVector<int, KeyValue>
|
||||
{
|
||||
new KeyValue { Key = value, Value = value, },
|
||||
new KeyValue { Key = value + 1, Value = value + 1, },
|
||||
new KeyValue { Key = value + 2, Value = value + 2, },
|
||||
}
|
||||
};
|
||||
|
||||
byte[] buffer = new byte[Root.Serializer.GetMaxSize(root)];
|
||||
Root.Serializer.Write(buffer, root);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private static void VerifyRoot(Root item, int expectedValue)
|
||||
{
|
||||
Assert.Equal(expectedValue, item.InnerTable.X);
|
||||
Assert.Equal(expectedValue, item.RefStruct.X);
|
||||
Assert.Equal(expectedValue, item.ValueStruct.Value.X);
|
||||
|
||||
foreach (var temp in item.VectorOfRefStruct)
|
||||
{
|
||||
Assert.Equal(expectedValue, temp.X);
|
||||
}
|
||||
|
||||
foreach (var temp in item.VectorOfTable)
|
||||
{
|
||||
Assert.Equal(expectedValue, temp.X);
|
||||
}
|
||||
|
||||
foreach (var temp in item.VectorOfValueStruct)
|
||||
{
|
||||
Assert.Equal(expectedValue, temp.X);
|
||||
}
|
||||
|
||||
foreach (var temp in item.VectorOfUnion)
|
||||
{
|
||||
if (temp.TryGet(out InnerTable? value))
|
||||
{
|
||||
Assert.Equal(expectedValue, value.X);
|
||||
}
|
||||
|
||||
if (temp.TryGet(out RefStruct? s))
|
||||
{
|
||||
Assert.Equal(expectedValue, s.X);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
int expected = expectedValue + i;
|
||||
Assert.True(item.IndexedVector.TryGetValue(expected, out KeyValue? value));
|
||||
Assert.Equal(expected, value.Key);
|
||||
Assert.Equal(expected, value.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private struct Visitor : InnerUnion.Visitor<object>
|
||||
{
|
||||
public object Visit(RefStruct item)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
public object Visit(ValueStruct item)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
public object Visit(InnerTable item)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
attribute "fs_serializer";
|
||||
attribute "fs_unsafeStructVector";
|
||||
attribute "fs_valueStruct";
|
||||
attribute "fs_writeThrough";
|
||||
attribute "fs_vector";
|
||||
|
||||
namespace FlatSharpEndToEndTests.PoolingTests;
|
||||
|
||||
struct RefStruct { x : int; }
|
||||
struct ValueStruct (fs_valueStruct) { x : int; }
|
||||
table InnerTable { x : int; }
|
||||
|
||||
union InnerUnion { RefStruct, ValueStruct, InnerTable }
|
||||
|
||||
table KeyValue
|
||||
{
|
||||
key : int (key);
|
||||
value : int;
|
||||
}
|
||||
|
||||
table Root (fs_serializer:"Lazy")
|
||||
{
|
||||
ref_struct : RefStruct;
|
||||
value_struct : ValueStruct;
|
||||
inner_table : InnerTable;
|
||||
|
||||
vector_of_ref_struct : [ RefStruct ];
|
||||
vector_of_value_struct : [ ValueStruct ];
|
||||
vector_of_table : [ InnerTable ];
|
||||
|
||||
vector_of_union : [ InnerUnion ];
|
||||
|
||||
indexed_vector : [ KeyValue ] (fs_vector:"IIndexedVector");
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright 2021 James Courtney
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
using FlatSharp.Internal;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace FlatSharpEndToEndTests.PoolingTests;
|
||||
|
||||
#if DEBUG
|
||||
public class TestObjectPool : IObjectPool
|
||||
{
|
||||
private readonly ConcurrentDictionary<Type, TypedPool> pool = new();
|
||||
|
||||
public void Return<T>(T item)
|
||||
{
|
||||
this.GetPool(typeof(T)).Return(item);
|
||||
}
|
||||
|
||||
public bool TryGet<T>(out T? value)
|
||||
{
|
||||
bool result = this.GetPool(typeof(T)).TryGet(out var obj);
|
||||
|
||||
value = (T?)obj;
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool IsInPool(object item)
|
||||
{
|
||||
return GetPool(item.GetType()).Contains(item);
|
||||
}
|
||||
|
||||
public int Count(object item)
|
||||
{
|
||||
return GetPool(item.GetType()).Count;
|
||||
}
|
||||
|
||||
private TypedPool GetPool(Type type)
|
||||
{
|
||||
return pool.GetOrAdd(type, new TypedPool());
|
||||
}
|
||||
|
||||
private class TypedPool
|
||||
{
|
||||
private readonly object syncRoot = new();
|
||||
private readonly HashSet<object> members = new();
|
||||
private readonly Queue<object> dequeuePool = new();
|
||||
|
||||
public int Count => members.Count;
|
||||
|
||||
public void Return(object item)
|
||||
{
|
||||
lock (syncRoot)
|
||||
{
|
||||
Assert.True(members.Add(item));
|
||||
dequeuePool.Enqueue(item);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGet(out object? item)
|
||||
{
|
||||
lock (syncRoot)
|
||||
{
|
||||
if (dequeuePool.Count > 0)
|
||||
{
|
||||
item = dequeuePool.Dequeue();
|
||||
Assert.True(members.Remove(item));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(object item)
|
||||
{
|
||||
lock (syncRoot)
|
||||
{
|
||||
return members.Contains(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -253,10 +253,6 @@ public class TypeModelTests
|
|||
() => RuntimeTypeModel.CreateFrom(typeof(TableOnDeserialized_RefParameter)));
|
||||
Assert.Equal(CreateError<TableOnDeserialized_RefParameter>(), ex.Message);
|
||||
|
||||
ex = Assert.Throws<InvalidFlatBufferDefinitionException>(
|
||||
() => RuntimeTypeModel.CreateFrom(typeof(TableOnDeserialized_OptionalParameter)));
|
||||
Assert.Equal(CreateError<TableOnDeserialized_OptionalParameter>(), ex.Message);
|
||||
|
||||
ex = Assert.Throws<InvalidFlatBufferDefinitionException>(
|
||||
() => RuntimeTypeModel.CreateFrom(typeof(TableOnDeserialized_NotProtected)));
|
||||
Assert.Equal(CreateError<TableOnDeserialized_NotProtected>(), ex.Message);
|
||||
|
@ -1398,7 +1394,7 @@ public class TypeModelTests
|
|||
{
|
||||
protected void OnFlatSharpDeserialized(out FlatBufferDeserializationContext context)
|
||||
{
|
||||
context = null;
|
||||
context = default;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1418,14 +1414,6 @@ public class TypeModelTests
|
|||
}
|
||||
}
|
||||
|
||||
[FlatBufferTable]
|
||||
public class TableOnDeserialized_OptionalParameter
|
||||
{
|
||||
protected void OnFlatSharpDeserialized(FlatBufferDeserializationContext context = null)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[FlatBufferTable]
|
||||
public class TableOnDeserialized_Multiple
|
||||
{
|
||||
|
|
|
@ -112,7 +112,7 @@ public class ConstructorTests
|
|||
this.Context = context;
|
||||
}
|
||||
|
||||
public FlatBufferDeserializationContext Context { get; }
|
||||
public FlatBufferDeserializationContext? Context { get; }
|
||||
|
||||
[FlatBufferItem(0)]
|
||||
public virtual OuterStruct? Struct { get; set; }
|
||||
|
@ -147,7 +147,7 @@ public class ConstructorTests
|
|||
{
|
||||
}
|
||||
|
||||
public FlatBufferDeserializationContext Context { get; }
|
||||
public FlatBufferDeserializationContext? Context { get; }
|
||||
|
||||
[FlatBufferItem(0)]
|
||||
public virtual int Item { get; set; }
|
||||
|
@ -170,7 +170,7 @@ public class ConstructorTests
|
|||
this.Context = context;
|
||||
}
|
||||
|
||||
public FlatBufferDeserializationContext Context { get; }
|
||||
public FlatBufferDeserializationContext? Context { get; }
|
||||
|
||||
[FlatBufferItem(0)]
|
||||
public virtual int Item { get; set; }
|
||||
|
|
|
@ -255,7 +255,7 @@ public class DeserializationOptionsTests
|
|||
var table = this.SerializeAndParse<IList<string>>(FlatBufferDeserializationOption.GreedyMutable, Strings);
|
||||
InputBuffer.AsSpan().Fill(0);
|
||||
|
||||
Assert.Equal(typeof(List<string>), table.Vector.GetType());
|
||||
Assert.Equal(typeof(PoolableList<string>), table.Vector.GetType());
|
||||
Assert.True(object.ReferenceEquals(table.Vector, table.Vector));
|
||||
|
||||
var vector = table.Vector;
|
||||
|
@ -277,7 +277,7 @@ public class DeserializationOptionsTests
|
|||
var table = this.SerializeAndParse<IReadOnlyList<string>>(FlatBufferDeserializationOption.GreedyMutable, Strings);
|
||||
InputBuffer.AsSpan().Fill(0);
|
||||
|
||||
Assert.Equal(typeof(List<string>), table.Vector.GetType());
|
||||
Assert.Equal(typeof(PoolableList<string>), table.Vector.GetType());
|
||||
Assert.True(object.ReferenceEquals(table.Vector, table.Vector));
|
||||
Assert.True(object.ReferenceEquals(table.Vector[5], table.Vector[5]));
|
||||
Assert.True(object.ReferenceEquals(table.First, table.First));
|
||||
|
|
|
@ -107,15 +107,14 @@ public class DeserializedConstructorTests
|
|||
TTable result = serializer.Parse(data);
|
||||
|
||||
Assert.Null(table.Context);
|
||||
Assert.Equal(item, result.Context.DeserializationOption);
|
||||
Assert.Equal(item, result.Struct.Context.DeserializationOption);
|
||||
Assert.False(object.ReferenceEquals(result.Context, result.Struct.Context));
|
||||
Assert.Equal(item, result.Context.Value.DeserializationOption);
|
||||
Assert.Equal(item, result.Struct.Context.Value.DeserializationOption);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IContextItem
|
||||
{
|
||||
FlatBufferDeserializationContext Context { get; }
|
||||
FlatBufferDeserializationContext? Context { get; }
|
||||
}
|
||||
|
||||
public interface IContextTable<TStruct>
|
||||
|
@ -128,7 +127,7 @@ public class DeserializedConstructorTests
|
|||
public class PublicContextConstructorTable<TStruct> : IContextItem, IContextTable<TStruct>
|
||||
where TStruct : IContextItem, new()
|
||||
{
|
||||
public FlatBufferDeserializationContext Context { get; }
|
||||
public FlatBufferDeserializationContext? Context { get; }
|
||||
|
||||
public PublicContextConstructorTable() { }
|
||||
|
||||
|
@ -147,7 +146,7 @@ public class DeserializedConstructorTests
|
|||
[FlatBufferStruct]
|
||||
public class PublicContextConstructorStruct : IContextItem
|
||||
{
|
||||
public FlatBufferDeserializationContext Context { get; }
|
||||
public FlatBufferDeserializationContext? Context { get; }
|
||||
|
||||
public PublicContextConstructorStruct() { }
|
||||
|
||||
|
@ -165,7 +164,7 @@ public class DeserializedConstructorTests
|
|||
public class ProtectedContextConstructorTable<TStruct> : IContextItem, IContextTable<TStruct>
|
||||
where TStruct : IContextItem, new()
|
||||
{
|
||||
public FlatBufferDeserializationContext Context { get; }
|
||||
public FlatBufferDeserializationContext? Context { get; }
|
||||
|
||||
public ProtectedContextConstructorTable() { }
|
||||
|
||||
|
@ -184,7 +183,7 @@ public class DeserializedConstructorTests
|
|||
[FlatBufferStruct]
|
||||
public class ProtectedContextConstructorStruct : IContextItem
|
||||
{
|
||||
public FlatBufferDeserializationContext Context { get; }
|
||||
public FlatBufferDeserializationContext? Context { get; }
|
||||
|
||||
public ProtectedContextConstructorStruct() { }
|
||||
|
||||
|
@ -201,7 +200,7 @@ public class DeserializedConstructorTests
|
|||
public class ProtectedInternalContextConstructorTable<TStruct> : IContextItem, IContextTable<TStruct>
|
||||
where TStruct : IContextItem, new()
|
||||
{
|
||||
public FlatBufferDeserializationContext Context { get; }
|
||||
public FlatBufferDeserializationContext? Context { get; }
|
||||
|
||||
public ProtectedInternalContextConstructorTable() { }
|
||||
|
||||
|
@ -220,7 +219,7 @@ public class DeserializedConstructorTests
|
|||
[FlatBufferStruct]
|
||||
public class ProtectedInternalContextConstructorStruct : IContextItem
|
||||
{
|
||||
public FlatBufferDeserializationContext Context { get; }
|
||||
public FlatBufferDeserializationContext? Context { get; }
|
||||
|
||||
public ProtectedInternalContextConstructorStruct() { }
|
||||
|
||||
|
@ -238,7 +237,7 @@ public class DeserializedConstructorTests
|
|||
public class PrivateContextConstructorTable<TStruct> : IContextItem, IContextTable<TStruct>
|
||||
where TStruct : IContextItem, new()
|
||||
{
|
||||
public FlatBufferDeserializationContext Context { get; }
|
||||
public FlatBufferDeserializationContext? Context { get; }
|
||||
|
||||
public PrivateContextConstructorTable() { }
|
||||
|
||||
|
@ -257,7 +256,7 @@ public class DeserializedConstructorTests
|
|||
[FlatBufferStruct]
|
||||
public class PrivateContextConstructorStruct : IContextItem
|
||||
{
|
||||
public FlatBufferDeserializationContext Context { get; }
|
||||
public FlatBufferDeserializationContext? Context { get; }
|
||||
|
||||
public PrivateContextConstructorStruct() { }
|
||||
|
||||
|
|
|
@ -272,7 +272,7 @@ public class VectorDeserializationTests
|
|||
|
||||
var parsed = serializer.Parse<RootTable<IList<string>>>(buffer);
|
||||
|
||||
Assert.Equal(typeof(List<string>), parsed.Vector.GetType());
|
||||
Assert.Equal(typeof(PoolableList<string>), parsed.Vector.GetType());
|
||||
Assert.False(parsed.Vector.IsReadOnly);
|
||||
|
||||
// Shouldn't throw.
|
||||
|
|
Загрузка…
Ссылка в новой задаче