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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Microsoft.OData.Serializer/Adapters/IPropertyWriter.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@

using System.Buffers;

namespace Microsoft.OData.Serializer;

public interface IPropertyWriter<TCustomState>
{
bool WriteProperty<T>(ReadOnlySpan<char> propertyName, T value, ODataWriterState<TCustomState> state);
void WritePropertyToBuffer<T>(
Action<IBufferWriter<byte>, T, ODataWriterState<TCustomState>> writeAction,
ReadOnlySpan<char> propertyName,
T value,
ODataWriterState<TCustomState> state);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.OData.Serializer;

public sealed class ODataElementAsyncEnumerableSelector<TResource, TElement>
: ODataElementAsyncEnumerableSelector<TResource, TElement, DefaultState>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.OData.Serializer;

public class ODataElementAsyncEnumerableSelector<TResource, TElement, TCustomState>
: ODataElementSelector<TResource, TCustomState>
{
public required Func<TResource, ODataWriterState<TCustomState>, IAsyncEnumerable<TElement>> GetElements { get; set; }

public Func<TResource, TElement, IValueWriter<TCustomState>, ODataWriterState<TCustomState>, bool>? WriteElement { get; set; }

internal override bool WriteElements(TResource resource, ODataWriterState<TCustomState> state)
{
IAsyncEnumerator<TElement> enumerator;
if (state.Stack.Current.CurrentEnumerator == null)
{
var elements = GetElements(resource, state);
enumerator = elements.GetAsyncEnumerator();
state.Stack.Current.CurrentEnumerator = enumerator;
// TODO: This may create too many tasks allocation,
// consider optimizing by avoiding AsTask() if value task already completed successfully
Task<bool> moveNextTask = enumerator.MoveNextAsync().AsTask();
state.Stack.Current.PendingTaskWithValue = moveNextTask;

// The task will be awaited at the serializer root.
// TODO: we are returning to the root for each iteration,
// we should avoid the overhead of the return to the root if the
// task is already completed successfully.
return false;
}

// Retrieve the enumerator from the state so we can resume writing from where we left off.
enumerator = (state.Stack.Current.CurrentEnumerator as IAsyncEnumerator<TElement>)!;
Debug.Assert(enumerator != null, "CurrentEnumerator should be of type IEnumerator<TProperty>. Possible bug in state management and property resume operation.");
bool didMoveNextSucceed = true;
var pendingTask = state.Stack.Current.PendingTaskWithValue;
if (pendingTask != null)
{
Task<bool> moveNextTask = pendingTask as Task<bool>;
Debug.Assert(moveNextTask != null, "PendingTaskWithValue should be Task<bool> when resuming an async enumerable.");
Debug.Assert(moveNextTask.IsCompletedSuccessfully, "PendingTaskWithValue should be completed successfully when resuming an async enumerable.");
didMoveNextSucceed = moveNextTask.GetAwaiter().GetResult();
state.Stack.Current.PendingTaskWithValue = null;
}

if (!didMoveNextSucceed)
{
// we reached the end of the collection;
return true;
}

var item = enumerator.Current;
if (!WriteElementImplementation(resource, item, state))
{
return false;
}

state.Stack.EndProperty();

state.Stack.Current.PendingTaskWithValue = enumerator.MoveNextAsync().AsTask();
return false;
}

protected virtual bool WriteElementImplementation(TResource resource, TElement element, ODataWriterState<TCustomState> state)
{
Debug.Assert(WriteElement != null, "WriteProperty should not be null if this method is called.");

return WriteElement(resource, element, DefaultValueWriter<TCustomState>.Instance, state);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.OData.Serializer;

using System.Collections;
using System.Diagnostics;

public class ODataElementEnumeratorSelector<TResource, TPropertyEnumerator, TElement, TCustomState>
: ODataElementSelector<TResource, TCustomState>
where TPropertyEnumerator : IEnumerator<TElement>
{
public required Func<TResource, ODataWriterState<TCustomState>, TPropertyEnumerator>
GetElementsEnumerator
{ get; set; }

public Func<TResource, TElement, IValueWriter<TCustomState>, ODataWriterState<TCustomState>, bool>?
WriteElement
{ get; set; }

internal override bool WriteElements(TResource resource, ODataWriterState<TCustomState> state)
{
bool isResuming = state.Stack.Current.CurrentEnumerator != null;

if (!isResuming)
{
// This fast path allows to write all properties without boxing if everything fits in the buffer.

// We get the enumerator without boxing.
TPropertyEnumerator enumerator = GetElementsEnumerator(resource, state);
if (!enumerator.MoveNext())
{
// If the collection is empty, we can return immediately.
return true;
}

do
{
if (state.ShouldFlush())
{
// We box here to store the enumerator.
state.Stack.Current.CurrentEnumerator = enumerator;
return false;
}

var propertyItem = enumerator.Current;
bool success = WriteElementImplementation(resource, propertyItem, state);
if (!success)
{
// Store the enumerator for resuming later. At this point, the enumerator is boxed.
state.Stack.Current.CurrentEnumerator = enumerator;
return false;
}

state.Stack.EndProperty();
}
while (enumerator.MoveNext());

// if we got here, then we wrote all properties without flushing, and without boxing.
return true;
}

// We get the boxed enumerator from the state
IEnumerator<TElement> boxedEnumerator = (state.Stack.Current.CurrentEnumerator as IEnumerator<TElement>)!;
Debug.Assert(boxedEnumerator != null, "CurrentEnumerator should be of type IEnumerator<TProperty>.");

do
{
if (state.ShouldFlush())
{
return false;
}

var propertyItem = boxedEnumerator.Current;

bool success = WriteElementImplementation(resource, propertyItem, state);
if (!success)
{
return false;
}

state.Stack.EndProperty();
} while (boxedEnumerator.MoveNext());

state.Stack.EndCollectionElement();
return true;
}

protected virtual bool WriteElementImplementation(TResource resource, TElement element, ODataWriterState<TCustomState> state)
{
Debug.Assert(WriteElement != null, "WriteProperty should not be null if this method is called.");

return WriteElement(resource, element, DefaultValueWriter<TCustomState>.Instance, state);
}
}
16 changes: 16 additions & 0 deletions src/Microsoft.OData.Serializer/Adapters/ODataElementSelector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.OData.Serializer;

public abstract class ODataElementSelector<TResource, TCustomState>
{
internal ODataElementSelector() // internal constructor to prevent external subclassing
{
}

internal abstract bool WriteElements(TResource resource, ODataWriterState<TCustomState> state);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.OData.Serializer
{
public sealed class ODataPropertyAsyncEnumerableSelector<TResource, TProperty>
: ODataPropertyAsyncEnumerableSelector<TResource, TProperty, DefaultState>
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.OData.Serializer
{
public class ODataPropertyAsyncEnumerableSelector<TResource, TProperty, TCustomState>
: ODataPropertySelector<TResource, TCustomState>
{
internal ODataPropertyAsyncEnumerableSelector() { } // prevent

public required Func<TResource, ODataWriterState<TCustomState>, IAsyncEnumerable<TProperty>>
GetProperties
{ get; set; }

public Func<TResource, TProperty, IPropertyWriter<TCustomState>, ODataWriterState<TCustomState>, bool>?
WriteProperty
{ get; set; }

internal override bool WriteProperties(TResource resource, ODataWriterState<TCustomState> state)
{
IAsyncEnumerator<TProperty> enumerator;
if (state.Stack.Current.CurrentEnumerator == null)
{
var properties = GetProperties(resource, state);
enumerator = properties.GetAsyncEnumerator();
state.Stack.Current.CurrentEnumerator = enumerator;
// TODO: This may create too many tasks allocation,
// consider optimizing by avoiding AsTask() if value task already completed successfully
Task<bool> moveNextTask = enumerator.MoveNextAsync().AsTask();
state.Stack.Current.PendingTaskWithValue = moveNextTask;

// The task will be awaited at the serializer root.
// TODO: we are returning to the root for each iteration,
// we should avoid the overhead of the return to the root if the
// task is already completed successfully.
return false;
}

// Retrieve the enumerator from the state so we can resume writing from where we left off.
enumerator = (state.Stack.Current.CurrentEnumerator as IAsyncEnumerator<TProperty>)!;
Debug.Assert(enumerator != null, "CurrentEnumerator should be of type IEnumerator<TProperty>. Possible bug in state management and property resume operation.");
bool didMoveNextSucceed = true;
var pendingTask = state.Stack.Current.PendingTaskWithValue;
if (pendingTask != null)
{
Task<bool> moveNextTask = pendingTask as Task<bool>;
Debug.Assert(moveNextTask != null, "PendingTaskWithValue should be Task<bool> when resuming an async enumerable.");
Debug.Assert(moveNextTask.IsCompletedSuccessfully, "PendingTaskWithValue should be completed successfully when resuming an async enumerable.");
didMoveNextSucceed = moveNextTask.GetAwaiter().GetResult();
state.Stack.Current.PendingTaskWithValue = null;
}

if (!didMoveNextSucceed)
{
// we reached the end of the collection;
return true;
}

var item = enumerator.Current;
if (!WritePropertyImplementation(resource, item, state))
{
return false;
}

state.Stack.EndProperty();

state.Stack.Current.PendingTaskWithValue = enumerator.MoveNextAsync().AsTask();
return false;
}

protected virtual bool WritePropertyImplementation(TResource resource, TProperty propertyItem, ODataWriterState<TCustomState> state)
{
Debug.Assert(WriteProperty != null, "WriteProperty should not be null if this method is called.");

return WriteProperty(resource, propertyItem, DefaultPropertyWriter<TCustomState>.Instance, state);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

namespace Microsoft.OData.Serializer;

public class ODataPropertyEnumerableSelector<TDeclaringType, TProperty>
public sealed class ODataPropertyEnumerableSelector<TDeclaringType, TProperty>
: ODataPropertyEnumerableSelector<TDeclaringType, TProperty, DefaultState>
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@ namespace Microsoft.OData.Serializer;

public abstract class ODataPropertySelector<TResource, TCustomState>
{
internal ODataPropertySelector() // internal constructor to prevent external subclassing
{
}

internal abstract bool WriteProperties(TResource resource, ODataWriterState<TCustomState> state);
}
4 changes: 4 additions & 0 deletions src/Microsoft.OData.Serializer/Adapters/ODataTypeInfoOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public class ODataTypeInfo<T, TCustomState> : ODataTypeInfo

public Func<T, ODataWriterState<TCustomState>, string>? GetEdmTypeName { get; init; }

public Func<T, ODataWriterState<TCustomState>, ODataValueKind>? GetValueKind { get; init; }

public IReadOnlyList<ODataPropertyInfo<T, TCustomState>>? Properties
{
get
Expand All @@ -40,6 +42,8 @@ public IReadOnlyList<ODataPropertyInfo<T, TCustomState>>? Properties

public ODataPropertySelector<T, TCustomState>? PropertySelector { get; init; }

public ODataElementSelector<T, TCustomState>? ElementSelector { get; init; }

// We expose two approaches to writing annotations:
// - shorthand GetXXXValue that returns the value, here we select a common, but "cheap" type to represent the value.
// - WriteXXXValue that accepts a constrained writer that can write the value. The write may have overloads that support different types for more flexibility and performance.
Expand Down
15 changes: 15 additions & 0 deletions src/Microsoft.OData.Serializer/Core/ODataValueKind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.OData.Serializer;

public enum ODataValueKind
{
Primitive,
Resource,
Collection,
Unknown
}
Loading