зеркало из https://github.com/microsoft/psi.git
Merged PR 31263: Release 0.10.16.1
Release build 0.10.16.1 into Master
This commit is contained in:
Родитель
67b1205f89
Коммит
4df7ff6ac6
|
@ -6,7 +6,7 @@
|
|||
<Company>Microsoft Corporation</Company>
|
||||
<Owners>microsoft,psi</Owners>
|
||||
<Authors>Microsoft</Authors>
|
||||
<AssemblyVersion>0.9.6.1</AssemblyVersion>
|
||||
<AssemblyVersion>0.10.16.1</AssemblyVersion>
|
||||
<FileVersion>$(AssemblyVersion)</FileVersion>
|
||||
<Version>$(AssemblyVersion)-beta</Version>
|
||||
<SignAssembly>false</SignAssembly>
|
||||
|
|
|
@ -10,6 +10,6 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation. All rights reserved.")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("c42c2219-487c-479e-87e7-90e1e47ab049")]
|
||||
[assembly: AssemblyVersion("0.9.6.1")]
|
||||
[assembly: AssemblyFileVersion("0.9.6.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.9.6.1-beta")]
|
||||
[assembly: AssemblyVersion("0.10.16.1")]
|
||||
[assembly: AssemblyFileVersion("0.10.16.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.10.16.1-beta")]
|
||||
|
|
|
@ -13,8 +13,8 @@ using namespace System::Security::Permissions;
|
|||
[assembly:AssemblyProductAttribute(L"OpenCVSampleInterop")];
|
||||
[assembly:AssemblyCompanyAttribute(L"Microsoft Corporation")];
|
||||
[assembly:AssemblyCopyrightAttribute(L"Copyright (c) Microsoft Corporation. All rights reserved.")];
|
||||
[assembly:AssemblyVersionAttribute("0.9.6.1")];
|
||||
[assembly:AssemblyFileVersionAttribute("0.9.6.1")];
|
||||
[assembly:AssemblyInformationalVersionAttribute("0.9.6.1-beta")];
|
||||
[assembly:AssemblyVersionAttribute("0.10.16.1")];
|
||||
[assembly:AssemblyFileVersionAttribute("0.10.16.1")];
|
||||
[assembly:AssemblyInformationalVersionAttribute("0.10.16.1-beta")];
|
||||
[assembly:ComVisible(false)];
|
||||
[assembly:CLSCompliantAttribute(true)];
|
||||
|
|
|
@ -39,14 +39,12 @@ namespace Microsoft
|
|||
|
||||
static void SaveImage(ImageBuffer ^img, System::String ^filename)
|
||||
{
|
||||
std::string fn = msclr::interop::marshal_as<std::string>(filename);
|
||||
cv::Mat matImg = WrapInMat(img);
|
||||
IplImage *iplImg = new IplImage(matImg);
|
||||
cvSaveImage(fn.c_str(), iplImg);
|
||||
delete iplImg;
|
||||
std::string fn = msclr::interop::marshal_as<std::string>(filename);
|
||||
cv::Mat matImg = WrapInMat(img);
|
||||
cv::imwrite(fn, matImg);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project InitialTargets="CheckVariable" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(OpenCVDir_V4)'!=''">
|
||||
<OpenCVIncludes>$(OpenCVDir_V4)\build\include</OpenCVIncludes>
|
||||
<OpenCVLibs Condition="'$(Configuration)'=='Debug'">$(OpenCVDir_V4)\build\x64\vc15\lib\opencv_world411d.lib</OpenCVLibs>
|
||||
<OpenCVLibs Condition="'$(Configuration)'=='Release'">$(OpenCVDir_V4)\build\x64\vc15\lib\opencv_world411.lib</OpenCVLibs>
|
||||
<OpenCVDefines>SUPPORT_OPENCV</OpenCVDefines>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(OpenCVDir_V4)'==''">
|
||||
<OpenCVIncludes />
|
||||
<OpenCVLibs />
|
||||
<OpenCVDefines />
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
|
@ -50,7 +61,7 @@
|
|||
<EnableManagedIncrementalBuild>true</EnableManagedIncrementalBuild>
|
||||
<LinkKeyFile>
|
||||
</LinkKeyFile>
|
||||
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);$(OpenCVDir)\build\include;$(OpenCVDir)\sources\modules\highgui\include;$(OpenCVDir)\sources\modules\core\include;$(OpenCVDir)\sources\modules\videostab\include;$(OpenCVDir)\sources\modules\imgcodecs\include;$(OpenCVDir)\sources\modules\imgproc\include;$(OpenCVDir)\sources\modules\videoio\include;;$(OpenCVDir)\sources\include</IncludePath>
|
||||
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath)</IncludePath>
|
||||
<OutDir>$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(ProjectDir)\$(Platform)\$(Configuration)\</IntDir>
|
||||
<CodeAnalysisRuleSet>..\..\..\Build\Microsoft.Psi.ruleset</CodeAnalysisRuleSet>
|
||||
|
@ -60,7 +71,7 @@
|
|||
<EnableManagedIncrementalBuild>true</EnableManagedIncrementalBuild>
|
||||
<LinkKeyFile>
|
||||
</LinkKeyFile>
|
||||
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);$(OpenCVDir)\build\include;$(OpenCVDir)\sources\modules\highgui\include;$(OpenCVDir)\sources\modules\core\include;$(OpenCVDir)\sources\modules\videostab\include;$(OpenCVDir)\sources\modules\imgcodecs\include;$(OpenCVDir)\sources\modules\imgproc\include;$(OpenCVDir)\sources\modules\videoio\include;;$(OpenCVDir)\sources\include</IncludePath>
|
||||
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath)</IncludePath>
|
||||
<OutDir>$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(ProjectDir)\$(Platform)\$(Configuration)\</IntDir>
|
||||
<CodeAnalysisRuleSet>..\..\..\Build\Microsoft.Psi.ruleset</CodeAnalysisRuleSet>
|
||||
|
@ -71,19 +82,19 @@
|
|||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(OpenCVIncludes);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>"$(OpenCVDir)\build\x64\vc14\lib\opencv_world330d.lib";kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>"$(OpenCVLibs)";kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)bin\</AdditionalLibraryDirectories>
|
||||
<TargetMachine>MachineX64</TargetMachine>
|
||||
</Link>
|
||||
<PostBuildEvent>
|
||||
<Command>if not exist $(OutDir)..\..\..\OpenCVSample\bin\Debug (mkdir $(OutDir)..\..\..\OpenCVSample\bin\Debug)
|
||||
copy $(OutDir)$(TargetName)$(TargetExt) $(OutDir)..\..\..\OpenCVSample\bin\Debug\$(TargetName)$(TargetExt)
|
||||
copy $(OpenCVDir)\build\x64\vc14\bin\opencv_world330d.dll $(OutDir)..\..\..\OpenCVSample\bin\Debug</Command>
|
||||
copy $(OpenCVLibs) $(OutDir)..\..\..\OpenCVSample\bin\Debug</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
|
@ -92,13 +103,13 @@ copy $(OpenCVDir)\build\x64\vc14\bin\opencv_world330d.dll $(OutDir)..\..\..\Open
|
|||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<AdditionalIncludeDirectories>$(WindowsSDK_IncludePath);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(OpenCVIncludes);$(WindowsSDK_IncludePath);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<BrowseInformation>true</BrowseInformation>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>"$(OpenCVDir)\build\x64\vc14\lib\opencv_world330.lib";kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>"$(OpenCVLibs)";kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)bin\</AdditionalLibraryDirectories>
|
||||
<TargetMachine>MachineX64</TargetMachine>
|
||||
</Link>
|
||||
|
@ -108,7 +119,7 @@ copy $(OpenCVDir)\build\x64\vc14\bin\opencv_world330d.dll $(OutDir)..\..\..\Open
|
|||
<PostBuildEvent>
|
||||
<Command>if not exist $(OutDir)..\..\..\OpenCVSample\bin\Release (mkdir $(OutDir)..\..\..\OpenCVSample\bin\Release)
|
||||
copy $(OutDir)$(TargetName)$(TargetExt) $(OutDir)..\..\..\OpenCVSample\bin\Release\$(TargetName)$(TargetExt)
|
||||
copy $(OpenCVDir)\build\x64\vc14\bin\opencv_world330.dll $(OutDir)..\..\..\OpenCVSample\bin\Release</Command>
|
||||
copy $(OpenCVLibs) $(OutDir)..\..\..\OpenCVSample\bin\Release</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
|
@ -139,10 +150,13 @@ copy $(OpenCVDir)\build\x64\vc14\bin\opencv_world330.dll $(OutDir)..\..\..\OpenC
|
|||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
<Target Name="OldBuild" DependsOnTargets="$(OldBuildDependsOn)" />
|
||||
<Target Name="CheckVariable">
|
||||
<Warning Text="In order to build this sample you must define the environment variable OpenCVDir to point to your Intel RealSense SDK installation" ContinueOnError="WarnAndContinue" Condition="'$(OpenCVDir)'==''" />
|
||||
<CreateProperty Value="" Condition="'$(OpenCVDir)'==''">
|
||||
<Output TaskParameter="Value" PropertyName="BuildDependsOn" />
|
||||
</CreateProperty>
|
||||
<Warning Text="In order to build this sample you must define the environment variable OpenCVDir_V4 to point to your OpenCV SDK installation" ContinueOnError="WarnAndContinue" Condition="'$(OpenCVDir_V4)'==''" />
|
||||
<CallTarget Targets="OldBuild" Condition="'$(OpenCVDir_V4)'!=''" />
|
||||
</Target>
|
||||
</Project>
|
||||
<PropertyGroup>
|
||||
<OldBuildDependsOn>$(BuildDependsOn)</OldBuildDependsOn>
|
||||
<BuildDependsOn>CheckVariable</BuildDependsOn>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" InitialTargets="CheckVariable">
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
|
@ -141,10 +141,13 @@
|
|||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="OldBuild" DependsOnTargets="$(OldBuildDependsOn)"/>
|
||||
<Target Name="CheckVariable">
|
||||
<Warning Text="In order to build this sample you must define the environment variable OpenCVDir to point to your Intel RealSense SDK installation" ContinueOnError="WarnAndContinue" Condition="'$(OpenCVDir)'==''" />
|
||||
<CreateProperty Value="" Condition="'$(OpenCVDir)'==''">
|
||||
<Output TaskParameter="Value" PropertyName="BuildDependsOn" />
|
||||
</CreateProperty>
|
||||
<Warning Text="In order to build this sample you must define the environment variable OpenCVDir_V4 to point to your OpenCV SDK installation" ContinueOnError="WarnAndContinue" Condition="'$(OpenCVDir_V4)'==''" />
|
||||
<CallTarget Targets="OldBuild" Condition="'$(OpenCVDir_V4)'!=''"/>
|
||||
</Target>
|
||||
</Project>
|
||||
<PropertyGroup>
|
||||
<OldBuildDependsOn>$(BuildDependsOn)</OldBuildDependsOn>
|
||||
<BuildDependsOn>CheckVariable</BuildDependsOn>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
@ -11,6 +11,6 @@ using System.Windows;
|
|||
[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation. All rights reserved.")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
|
||||
[assembly: AssemblyVersion("0.9.6.1")]
|
||||
[assembly: AssemblyFileVersion("0.9.6.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.9.6.1-beta")]
|
||||
[assembly: AssemblyVersion("0.10.16.1")]
|
||||
[assembly: AssemblyFileVersion("0.10.16.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.10.16.1-beta")]
|
||||
|
|
|
@ -4,4 +4,4 @@ This sample demostrates how to integrate OpenCV with Platform for Situated Intel
|
|||
1) OpenCVSample.csproj is the main sample. It creates a webcam source and sends the images from the camera to OpenCV to convert them to gray scale.
|
||||
2) OpenCVSampleInterop is a C++ project that is used to interop between C# and OpenCV.
|
||||
|
||||
The OpenCVSample.Interop project is the interop layer between the sample (project OpenCVSample) and OpenCV. In order to build this project you will need OpenCV installed on your machine. OpenCV can be obtained [here](http://opencv.org/releases.html). The sample relies on version 3.3.0. You will need to set an environment variable named "OpenCVDir" that points to your OpenCV installation. The path should be the root of OpenCV which contains the "sources" directory (along with the license). For instance, "D:\cv3.3\opencv".
|
||||
The OpenCVSample.Interop project is the interop layer between the sample (project OpenCVSample) and OpenCV. In order to build this project you will need OpenCV installed on your machine. OpenCV can be obtained [here](http://opencv.org/releases.html). The sample relies on version 4.1.1. You will need to set an environment variable named "OpenCVDir_V4" that points to your OpenCV installation. The path should be the root of OpenCV which contains the "sources" directory (along with the license). For instance, "D:\OpenCV-4.1.1\opencv".
|
||||
|
|
|
@ -10,6 +10,6 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation. All rights reserved.")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("c5ab7394-68fb-4641-bdae-8a956edc44a9")]
|
||||
[assembly: AssemblyVersion("0.9.6.1")]
|
||||
[assembly: AssemblyFileVersion("0.9.6.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.9.6.1-beta")]
|
||||
[assembly: AssemblyVersion("0.10.16.1")]
|
||||
[assembly: AssemblyFileVersion("0.10.16.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.10.16.1-beta")]
|
||||
|
|
|
@ -10,6 +10,6 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation. All rights reserved.")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("f2c129ec-e1be-4f37-aa91-c48d1b98d75a")]
|
||||
[assembly: AssemblyVersion("0.9.6.1")]
|
||||
[assembly: AssemblyFileVersion("0.9.6.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.9.6.1-beta")]
|
||||
[assembly: AssemblyVersion("0.10.16.1")]
|
||||
[assembly: AssemblyFileVersion("0.10.16.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.10.16.1-beta")]
|
||||
|
|
|
@ -11,6 +11,6 @@ using System.Windows;
|
|||
[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation. All rights reserved.")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
|
||||
[assembly: AssemblyVersion("0.9.6.1")]
|
||||
[assembly: AssemblyFileVersion("0.9.6.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.9.6.1-beta")]
|
||||
[assembly: AssemblyVersion("0.10.16.1")]
|
||||
[assembly: AssemblyFileVersion("0.10.16.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.10.16.1-beta")]
|
||||
|
|
|
@ -386,7 +386,7 @@ namespace Test.Psi.Data
|
|||
(pipeline, importer, exporter, parameter) =>
|
||||
{
|
||||
var inputStream = importer.OpenStream<int>("Root");
|
||||
var derivedStream = inputStream.Sample(TimeSpan.FromMinutes(1)).Select(x => x * parameter).Write("DerivedStream", exporter);
|
||||
var derivedStream = inputStream.Sample(TimeSpan.FromMinutes(1), RelativeTimeInterval.Infinite).Select(x => x * parameter).Write("DerivedStream", exporter);
|
||||
|
||||
// add a dummy source and propose a long time interval so that the operation will block (and eventually be cancelled)
|
||||
var generator = Generators.Repeat(pipeline, 0, int.MaxValue, TimeSpan.FromMilliseconds(1000));
|
||||
|
@ -424,7 +424,7 @@ namespace Test.Psi.Data
|
|||
using (var p = Pipeline.Create())
|
||||
{
|
||||
var store = Store.Create(p, storeName, storePath);
|
||||
var root = Generators.Sequence(p, 0, i => i + 1, 10).Write("Root", store);
|
||||
var root = Generators.Sequence(p, 0, i => i + 1, 10, TimeSpan.FromTicks(1)).Write("Root", store);
|
||||
p.Run();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ namespace Test.Psi.Data
|
|||
using (var p = Pipeline.Create("write"))
|
||||
{
|
||||
var writeStore = Store.Create(p, name, this.path);
|
||||
var seq = Generators.Sequence(p, 0, i => i + 1, count);
|
||||
var seq = Generators.Sequence(p, 0, i => i + 1, count, TimeSpan.FromTicks(1));
|
||||
seq.Write("seq", writeStore);
|
||||
seq.Do((m, e) => before[m] = e);
|
||||
p.Run();
|
||||
|
@ -74,7 +74,7 @@ namespace Test.Psi.Data
|
|||
using (var p = Pipeline.Create("write"))
|
||||
{
|
||||
var writeStore = Store.Create(p, name, this.path);
|
||||
var seq = Generators.Sequence(p, 0, i => i + 1, count);
|
||||
var seq = Generators.Sequence(p, 0, i => i + 1, count, TimeSpan.FromTicks(1));
|
||||
var big = seq.Select(i => bytes.Select(_ => i).ToArray());
|
||||
seq.Write("seq", writeStore);
|
||||
big.Write("big", writeStore, largeMessages: true);
|
||||
|
|
|
@ -1,79 +1,80 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Imaging
|
||||
{
|
||||
using Microsoft.Psi.Common;
|
||||
using Microsoft.Psi.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// ImageCompressor defines an object used by the serialization layer
|
||||
/// for compressing streams of images in a generic fashion. This object
|
||||
/// should not be called directly but instead if used by Microsoft.Psi.Imaging.
|
||||
/// </summary>
|
||||
public class ImageCompressor : IImageCompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageCompressor"/> class.
|
||||
/// </summary>
|
||||
public ImageCompressor()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageCompressor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="compressionMethod">Compression method to be used by compressor.</param>
|
||||
public ImageCompressor(CompressionMethod compressionMethod)
|
||||
{
|
||||
this.CompressionMethod = compressionMethod;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the compression method being used by the compressor.
|
||||
/// </summary>
|
||||
public CompressionMethod CompressionMethod { get; set; } = CompressionMethod.PNG;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Serialize(BufferWriter writer, Image instance, SerializationContext context)
|
||||
{
|
||||
IBitmapEncoder encoder = null;
|
||||
switch (this.CompressionMethod)
|
||||
{
|
||||
case CompressionMethod.JPEG:
|
||||
encoder = new JpegBitmapEncoder { QualityLevel = 90 };
|
||||
break;
|
||||
case CompressionMethod.PNG:
|
||||
encoder = new PngBitmapEncoder();
|
||||
break;
|
||||
case CompressionMethod.None:
|
||||
break;
|
||||
}
|
||||
|
||||
if (encoder != null)
|
||||
{
|
||||
using (var sharedEncodedImage = EncodedImagePool.GetOrCreate())
|
||||
{
|
||||
sharedEncodedImage.Resource.EncodeFrom(instance, encoder);
|
||||
Serializer.Serialize(writer, sharedEncodedImage, context);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Serializer.Serialize(writer, instance, context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Deserialize(BufferReader reader, ref Image target, SerializationContext context)
|
||||
{
|
||||
Shared<EncodedImage> encodedImage = null;
|
||||
Serializer.Deserialize(reader, ref encodedImage, context);
|
||||
using (var image = ImagePool.GetOrCreate(encodedImage.Resource.Width, encodedImage.Resource.Height, PixelFormat.BGR_24bpp))
|
||||
{
|
||||
encodedImage.Resource.DecodeTo(image.Resource);
|
||||
target = image.Resource.DeepClone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Imaging
|
||||
{
|
||||
using Microsoft.Psi.Common;
|
||||
using Microsoft.Psi.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// ImageCompressor defines an object used by the serialization layer
|
||||
/// for compressing streams of images in a generic fashion. This object
|
||||
/// should not be called directly but instead if used by Microsoft.Psi.Imaging.
|
||||
/// </summary>
|
||||
public class ImageCompressor : IImageCompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageCompressor"/> class.
|
||||
/// </summary>
|
||||
public ImageCompressor()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageCompressor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="compressionMethod">Compression method to be used by compressor.</param>
|
||||
public ImageCompressor(CompressionMethod compressionMethod)
|
||||
{
|
||||
this.CompressionMethod = compressionMethod;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the compression method being used by the compressor.
|
||||
/// </summary>
|
||||
public CompressionMethod CompressionMethod { get; set; } = CompressionMethod.PNG;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Serialize(BufferWriter writer, Image instance, SerializationContext context)
|
||||
{
|
||||
// Note: encoder instances are created here because they may have thread affinity.
|
||||
IBitmapEncoder encoder = null;
|
||||
switch (this.CompressionMethod)
|
||||
{
|
||||
case CompressionMethod.JPEG:
|
||||
encoder = new JpegBitmapEncoder { QualityLevel = 90 };
|
||||
break;
|
||||
case CompressionMethod.PNG:
|
||||
encoder = new PngBitmapEncoder();
|
||||
break;
|
||||
case CompressionMethod.None:
|
||||
break;
|
||||
}
|
||||
|
||||
if (encoder != null)
|
||||
{
|
||||
using (var sharedEncodedImage = EncodedImagePool.GetOrCreate())
|
||||
{
|
||||
sharedEncodedImage.Resource.EncodeFrom(instance, encoder.Encode);
|
||||
Serializer.Serialize(writer, sharedEncodedImage, context);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Serializer.Serialize(writer, instance, context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Deserialize(BufferReader reader, ref Image target, SerializationContext context)
|
||||
{
|
||||
Shared<EncodedImage> encodedImage = null;
|
||||
Serializer.Deserialize(reader, ref encodedImage, context);
|
||||
using (var image = ImagePool.GetOrCreate(encodedImage.Resource.Width, encodedImage.Resource.Height, PixelFormat.BGR_24bpp))
|
||||
{
|
||||
ImageDecoder.DecodeTo(encodedImage.Resource, image.Resource);
|
||||
target = image.Resource.DeepClone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
|
||||
namespace Microsoft.Psi.Imaging
|
||||
{
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Psi;
|
||||
using Microsoft.Psi.Components;
|
||||
using Microsoft.Psi.Imaging;
|
||||
using SkiaSharp;
|
||||
|
||||
/// <summary>
|
||||
/// Pipeline component for decoding an image.
|
||||
|
@ -21,6 +22,17 @@ namespace Microsoft.Psi.Imaging
|
|||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes an encoded image into the given image instance.
|
||||
/// </summary>
|
||||
/// <param name="encodedImage">Encoded image to decode.</param>
|
||||
/// <param name="image">Image into which to decode.</param>
|
||||
public static void DecodeTo(EncodedImage encodedImage, Image image)
|
||||
{
|
||||
var decoded = SKBitmap.Decode(encodedImage.GetStream());
|
||||
Marshal.Copy(decoded.Bytes, 0, image.ImageData, decoded.ByteCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pipeline callback method for decoding a sample.
|
||||
/// </summary>
|
||||
|
@ -30,7 +42,7 @@ namespace Microsoft.Psi.Imaging
|
|||
{
|
||||
using (var image = ImagePool.GetOrCreate(encodedImage.Resource.Width, encodedImage.Resource.Height, PixelFormat.BGR_24bpp))
|
||||
{
|
||||
encodedImage.Resource.DecodeTo(image.Resource);
|
||||
DecodeTo(encodedImage.Resource, image.Resource);
|
||||
this.Out.Post(image, e.OriginatingTime);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
namespace Microsoft.Psi.Imaging
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Psi;
|
||||
using Microsoft.Psi.Components;
|
||||
|
||||
|
@ -37,7 +38,7 @@ namespace Microsoft.Psi.Imaging
|
|||
|
||||
using (var sharedEncodedImage = EncodedImagePool.GetOrCreate())
|
||||
{
|
||||
sharedEncodedImage.Resource.EncodeFrom(sharedImage.Resource, encoder);
|
||||
sharedEncodedImage.Resource.EncodeFrom(sharedImage.Resource, encoder.Encode);
|
||||
this.Out.Post(sharedEncodedImage, e.OriginatingTime);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,177 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Imaging
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
/// <summary>
|
||||
/// Defines an encoded image. Currently only the following image formats are supported:
|
||||
/// - PixelFormat.BGR_24bpp
|
||||
/// - PixelFormat.Gray_8bpp
|
||||
/// - PixelFormats.BGRX_32bpp.
|
||||
/// </summary>
|
||||
public class EncodedImage : IDisposable
|
||||
{
|
||||
private MemoryStream stream;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EncodedImage"/> class.
|
||||
/// </summary>
|
||||
public EncodedImage()
|
||||
{
|
||||
this.stream = new MemoryStream();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EncodedImage"/> class.
|
||||
/// </summary>
|
||||
/// <param name="width">Width of image in pixels.</param>
|
||||
/// <param name="height">Height of image in pixels.</param>
|
||||
/// <param name="contents">Byte array used to initialize the image data.</param>
|
||||
public EncodedImage(int width, int height, byte[] contents)
|
||||
{
|
||||
this.Width = width;
|
||||
this.Height = height;
|
||||
this.stream = new MemoryStream();
|
||||
this.stream.Write(contents, 0, contents.Length);
|
||||
this.stream.Position = 0;
|
||||
this.CountBytes = contents.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the width of the image in pixels.
|
||||
/// </summary>
|
||||
public int Width { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the height of the image in pixels.
|
||||
/// </summary>
|
||||
public int Height { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets number of bytes of data in the image.
|
||||
/// </summary>
|
||||
public int CountBytes { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Releases the image.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.stream.Dispose();
|
||||
this.stream = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the image data as a byte array.
|
||||
/// </summary>
|
||||
/// <returns>Byte array containing the image data.</returns>
|
||||
public byte[] GetBuffer()
|
||||
{
|
||||
return this.stream.GetBuffer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the image data as a byte array.
|
||||
/// </summary>
|
||||
/// <param name="newLength">Number of bytes to return.</param>/>
|
||||
/// <returns>Byte array containing the image data.</returns>
|
||||
public byte[] GetBuffer(int newLength)
|
||||
{
|
||||
this.stream.SetLength(newLength);
|
||||
return this.stream.GetBuffer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compresses an image using the specified encoder.
|
||||
/// </summary>
|
||||
/// <param name="image">Image to compress.</param>
|
||||
/// <param name="encoder">Encoder to use to compress.</param>
|
||||
public void EncodeFrom(Image image, BitmapEncoder encoder)
|
||||
{
|
||||
System.Windows.Media.PixelFormat format;
|
||||
if (image.PixelFormat == Imaging.PixelFormat.BGR_24bpp)
|
||||
{
|
||||
format = System.Windows.Media.PixelFormats.Bgr24;
|
||||
}
|
||||
else if (image.PixelFormat == Imaging.PixelFormat.Gray_16bpp)
|
||||
{
|
||||
format = System.Windows.Media.PixelFormats.Gray16;
|
||||
}
|
||||
else if (image.PixelFormat == Imaging.PixelFormat.Gray_8bpp)
|
||||
{
|
||||
format = System.Windows.Media.PixelFormats.Gray8;
|
||||
}
|
||||
else
|
||||
{
|
||||
format = System.Windows.Media.PixelFormats.Bgr32;
|
||||
}
|
||||
|
||||
BitmapSource bitmapSource = BitmapSource.Create(image.Width, image.Height, 96, 96, format, null, image.ImageData, image.Stride * image.Height, image.Stride);
|
||||
encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
|
||||
this.stream.Position = 0;
|
||||
encoder.Save(this.stream);
|
||||
this.stream.Flush();
|
||||
this.Width = image.Width;
|
||||
this.Height = image.Height;
|
||||
this.CountBytes = (int)this.stream.Position;
|
||||
this.stream.Position = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the pixel format of the image.
|
||||
/// </summary>
|
||||
/// <returns>Returns the image's pixel format.</returns>
|
||||
public Imaging.PixelFormat GetPixelFormat()
|
||||
{
|
||||
this.stream.Position = 0;
|
||||
var decoder = BitmapDecoder.Create(this.stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
|
||||
BitmapSource bitmapSource = decoder.Frames[0];
|
||||
if (bitmapSource.Format == System.Windows.Media.PixelFormats.Bgr24)
|
||||
{
|
||||
return Imaging.PixelFormat.BGR_24bpp;
|
||||
}
|
||||
else if (bitmapSource.Format == System.Windows.Media.PixelFormats.Gray16)
|
||||
{
|
||||
return Imaging.PixelFormat.Gray_16bpp;
|
||||
}
|
||||
else if (bitmapSource.Format == System.Windows.Media.PixelFormats.Gray8)
|
||||
{
|
||||
return Imaging.PixelFormat.Gray_8bpp;
|
||||
}
|
||||
else if (bitmapSource.Format == System.Windows.Media.PixelFormats.Bgr32)
|
||||
{
|
||||
return Imaging.PixelFormat.BGRX_32bpp;
|
||||
}
|
||||
else if (bitmapSource.Format == System.Windows.Media.PixelFormats.Bgra32)
|
||||
{
|
||||
return Imaging.PixelFormat.BGRA_32bpp;
|
||||
}
|
||||
else if (bitmapSource.Format == System.Windows.Media.PixelFormats.Rgba64)
|
||||
{
|
||||
return Imaging.PixelFormat.RGBA_64bpp;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException("Format not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses the current image into another another image.
|
||||
/// </summary>
|
||||
/// <param name="image">Image used to store decompressed results.</param>
|
||||
public void DecodeTo(Image image)
|
||||
{
|
||||
this.stream.Position = 0;
|
||||
var decoder = BitmapDecoder.Create(this.stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
|
||||
BitmapSource bitmapSource = decoder.Frames[0];
|
||||
bitmapSource.CopyPixels(Int32Rect.Empty, image.ImageData, image.Stride * image.Height, image.Stride);
|
||||
this.stream.Position = 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Imaging
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a pool of shared encoded images.
|
||||
/// </summary>
|
||||
public static class EncodedImagePool
|
||||
{
|
||||
private static readonly SharedPool<EncodedImage> Instance = new SharedPool<EncodedImage>(() => new EncodedImage(), 10);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates an encoded image from the pool.
|
||||
/// </summary>
|
||||
/// <returns>A shared encoded image from the pool.</returns>
|
||||
public static Shared<EncodedImage> GetOrCreate()
|
||||
{
|
||||
return Instance.GetOrCreate();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,85 +1,88 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Imaging
|
||||
{
|
||||
using System.Windows.Media.Imaging;
|
||||
using Microsoft.Psi.Common;
|
||||
using Microsoft.Psi.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// ImageCompressor defines an object used by the serialization layer
|
||||
/// for compressing streams of images in a generic fashion. This object
|
||||
/// should not be called directly but instead if used by Microsoft.Psi.Imaging.
|
||||
/// </summary>
|
||||
public class ImageCompressor : IImageCompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageCompressor"/> class.
|
||||
/// </summary>
|
||||
public ImageCompressor()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageCompressor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="compressionMethod">Compression method to be used by compressor.</param>
|
||||
public ImageCompressor(CompressionMethod compressionMethod)
|
||||
{
|
||||
this.CompressionMethod = compressionMethod;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the compression method being used by the compressor.
|
||||
/// </summary>
|
||||
public CompressionMethod CompressionMethod { get; set; } = CompressionMethod.PNG;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Serialize(BufferWriter writer, Image instance, SerializationContext context)
|
||||
{
|
||||
BitmapEncoder encoder = null;
|
||||
switch (this.CompressionMethod)
|
||||
{
|
||||
case CompressionMethod.JPEG:
|
||||
encoder = new JpegBitmapEncoder { QualityLevel = 90 };
|
||||
break;
|
||||
case CompressionMethod.PNG:
|
||||
encoder = new PngBitmapEncoder();
|
||||
break;
|
||||
case CompressionMethod.None:
|
||||
break;
|
||||
}
|
||||
|
||||
if (encoder != null)
|
||||
{
|
||||
using (var sharedEncodedImage = EncodedImagePool.GetOrCreate())
|
||||
{
|
||||
sharedEncodedImage.Resource.EncodeFrom(instance, encoder);
|
||||
Serializer.Serialize(writer, sharedEncodedImage, context);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Serializer.Serialize(writer, instance, context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Deserialize(BufferReader reader, ref Image target, SerializationContext context)
|
||||
{
|
||||
Shared<EncodedImage> encodedImage = null;
|
||||
Serializer.Deserialize(reader, ref encodedImage, context);
|
||||
using (var image = ImagePool.GetOrCreate(encodedImage.Resource.Width, encodedImage.Resource.Height, Imaging.PixelFormat.BGR_24bpp))
|
||||
{
|
||||
encodedImage.Resource.DecodeTo(image.Resource);
|
||||
target = image.Resource.DeepClone();
|
||||
}
|
||||
|
||||
if (encodedImage != null)
|
||||
{
|
||||
encodedImage.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Imaging
|
||||
{
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Microsoft.Psi.Common;
|
||||
using Microsoft.Psi.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// ImageCompressor defines an object used by the serialization layer
|
||||
/// for compressing streams of images in a generic fashion. This object
|
||||
/// should not be called directly but instead if used by Microsoft.Psi.Imaging.
|
||||
/// </summary>
|
||||
public class ImageCompressor : IImageCompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageCompressor"/> class.
|
||||
/// </summary>
|
||||
public ImageCompressor()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageCompressor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="compressionMethod">Compression method to be used by compressor.</param>
|
||||
public ImageCompressor(CompressionMethod compressionMethod)
|
||||
{
|
||||
this.CompressionMethod = compressionMethod;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the compression method being used by the compressor.
|
||||
/// </summary>
|
||||
public CompressionMethod CompressionMethod { get; set; } = CompressionMethod.PNG;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Serialize(BufferWriter writer, Image instance, SerializationContext context)
|
||||
{
|
||||
// Note: encoder instances are created here because they (in the case of media foundation) may have thread affinity.
|
||||
BitmapEncoder encoder = null;
|
||||
switch (this.CompressionMethod)
|
||||
{
|
||||
case CompressionMethod.JPEG:
|
||||
encoder = new JpegBitmapEncoder { QualityLevel = 90 };
|
||||
break;
|
||||
case CompressionMethod.PNG:
|
||||
encoder = new PngBitmapEncoder();
|
||||
break;
|
||||
case CompressionMethod.None:
|
||||
break;
|
||||
}
|
||||
|
||||
if (encoder != null)
|
||||
{
|
||||
using (var sharedEncodedImage = EncodedImagePool.GetOrCreate())
|
||||
{
|
||||
ImageEncoder.EncodeFrom(sharedEncodedImage.Resource, instance, encoder);
|
||||
Serializer.Serialize(writer, sharedEncodedImage, context);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Serializer.Serialize(writer, instance, context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Deserialize(BufferReader reader, ref Image target, SerializationContext context)
|
||||
{
|
||||
Shared<EncodedImage> encodedImage = null;
|
||||
Serializer.Deserialize(reader, ref encodedImage, context);
|
||||
using (var image = ImagePool.GetOrCreate(encodedImage.Resource.Width, encodedImage.Resource.Height, Imaging.PixelFormat.BGR_24bpp))
|
||||
{
|
||||
ImageDecoder.DecodeTo(encodedImage.Resource, image.Resource);
|
||||
target = image.Resource.DeepClone();
|
||||
}
|
||||
|
||||
if (encodedImage != null)
|
||||
{
|
||||
encodedImage.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,38 +1,92 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Imaging
|
||||
{
|
||||
using Microsoft.Psi;
|
||||
using Microsoft.Psi.Components;
|
||||
using Microsoft.Psi.Imaging;
|
||||
|
||||
/// <summary>
|
||||
/// Component that decodes an image using a specified decoder (e.g. JPEG, PNG).
|
||||
/// </summary>
|
||||
public class ImageDecoder : ConsumerProducer<Shared<EncodedImage>, Shared<Image>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageDecoder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Pipeline this component is a part of.</param>
|
||||
public ImageDecoder(Pipeline pipeline)
|
||||
: base(pipeline)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pipeline callback method for decoding a sample.
|
||||
/// </summary>
|
||||
/// <param name="encodedImage">Encoded image to decode.</param>
|
||||
/// <param name="e">Pipeline information about the sample.</param>
|
||||
protected override void Receive(Shared<EncodedImage> encodedImage, Envelope e)
|
||||
{
|
||||
using (var image = ImagePool.GetOrCreate(encodedImage.Resource.Width, encodedImage.Resource.Height, Imaging.PixelFormat.BGR_24bpp))
|
||||
{
|
||||
encodedImage.Resource.DecodeTo(image.Resource);
|
||||
this.Out.Post(image, e.OriginatingTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Imaging
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Microsoft.Psi;
|
||||
using Microsoft.Psi.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component that decodes an image using a specified decoder (e.g. JPEG, PNG).
|
||||
/// </summary>
|
||||
public class ImageDecoder : ConsumerProducer<Shared<EncodedImage>, Shared<Image>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageDecoder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Pipeline this component is a part of.</param>
|
||||
public ImageDecoder(Pipeline pipeline)
|
||||
: base(pipeline)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes an encoded image into the given image instance.
|
||||
/// </summary>
|
||||
/// <param name="encodedImage">Encoded image to decode.</param>
|
||||
/// <param name="image">Image into which to decode.</param>
|
||||
public static void DecodeTo(EncodedImage encodedImage, Image image)
|
||||
{
|
||||
var decoder = BitmapDecoder.Create(encodedImage.GetStream(), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
|
||||
BitmapSource bitmapSource = decoder.Frames[0];
|
||||
bitmapSource.CopyPixels(Int32Rect.Empty, image.ImageData, image.Stride * image.Height, image.Stride);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the pixel format of the image.
|
||||
/// </summary>
|
||||
/// <param name="encodedImage">Encoded image from which to get pixel format.</param>
|
||||
/// <returns>Returns the image's pixel format.</returns>
|
||||
public static PixelFormat GetPixelFormat(EncodedImage encodedImage)
|
||||
{
|
||||
var decoder = BitmapDecoder.Create(encodedImage.GetStream(), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
|
||||
BitmapSource bitmapSource = decoder.Frames[0];
|
||||
if (bitmapSource.Format == System.Windows.Media.PixelFormats.Bgr24)
|
||||
{
|
||||
return PixelFormat.BGR_24bpp;
|
||||
}
|
||||
else if (bitmapSource.Format == System.Windows.Media.PixelFormats.Gray16)
|
||||
{
|
||||
return PixelFormat.Gray_16bpp;
|
||||
}
|
||||
else if (bitmapSource.Format == System.Windows.Media.PixelFormats.Gray8)
|
||||
{
|
||||
return PixelFormat.Gray_8bpp;
|
||||
}
|
||||
else if (bitmapSource.Format == System.Windows.Media.PixelFormats.Bgr32)
|
||||
{
|
||||
return PixelFormat.BGRX_32bpp;
|
||||
}
|
||||
else if (bitmapSource.Format == System.Windows.Media.PixelFormats.Bgra32)
|
||||
{
|
||||
return PixelFormat.BGRA_32bpp;
|
||||
}
|
||||
else if (bitmapSource.Format == System.Windows.Media.PixelFormats.Rgba64)
|
||||
{
|
||||
return PixelFormat.RGBA_64bpp;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException("Format not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pipeline callback method for decoding a sample.
|
||||
/// </summary>
|
||||
/// <param name="encodedImage">Encoded image to decode.</param>
|
||||
/// <param name="e">Pipeline information about the sample.</param>
|
||||
protected override void Receive(Shared<EncodedImage> encodedImage, Envelope e)
|
||||
{
|
||||
using (var image = ImagePool.GetOrCreate(encodedImage.Resource.Width, encodedImage.Resource.Height, Imaging.PixelFormat.BGR_24bpp))
|
||||
{
|
||||
DecodeTo(encodedImage.Resource, image.Resource);
|
||||
this.Out.Post(image, e.OriginatingTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,46 +1,81 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Imaging
|
||||
{
|
||||
using System;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Microsoft.Psi;
|
||||
using Microsoft.Psi.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component that encodes an image using a specified encoder (e.g. JPEG, PNG).
|
||||
/// </summary>
|
||||
public class ImageEncoder : ConsumerProducer<Shared<Image>, Shared<EncodedImage>>
|
||||
{
|
||||
private readonly Func<BitmapEncoder> encoderFn;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageEncoder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Pipeline this component is a part of.</param>
|
||||
/// <param name="encoderFn">Callback method for encoding a single image sample.</param>
|
||||
public ImageEncoder(Pipeline pipeline, Func<BitmapEncoder> encoderFn)
|
||||
: base(pipeline)
|
||||
{
|
||||
this.encoderFn = encoderFn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pipeline callback function for encoding an image sample.
|
||||
/// </summary>
|
||||
/// <param name="sharedImage">Image to be encoded.</param>
|
||||
/// <param name="e">Pipeline information about the sample.</param>
|
||||
protected override void Receive(Shared<Image> sharedImage, Envelope e)
|
||||
{
|
||||
// the encoder has thread affinity, so we need to re-create it (we can't dispatch the call since we sdon't know if the thread that created us is pumping messages)
|
||||
var encoder = this.encoderFn();
|
||||
|
||||
using (var sharedEncodedImage = EncodedImagePool.GetOrCreate())
|
||||
{
|
||||
sharedEncodedImage.Resource.EncodeFrom(sharedImage.Resource, encoder);
|
||||
this.Out.Post(sharedEncodedImage, e.OriginatingTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Imaging
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Microsoft.Psi;
|
||||
using Microsoft.Psi.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component that encodes an image using a specified encoder (e.g. JPEG, PNG).
|
||||
/// </summary>
|
||||
public class ImageEncoder : ConsumerProducer<Shared<Image>, Shared<EncodedImage>>
|
||||
{
|
||||
private readonly Func<BitmapEncoder> encoderFn;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageEncoder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Pipeline this component is a part of.</param>
|
||||
/// <param name="encoderFn">Callback method for encoding a single image sample.</param>
|
||||
public ImageEncoder(Pipeline pipeline, Func<BitmapEncoder> encoderFn)
|
||||
: base(pipeline)
|
||||
{
|
||||
this.encoderFn = encoderFn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes an image in-place into the given encoded image instance using the specified encoder.
|
||||
/// </summary>
|
||||
/// <param name="encodedImage">Encoded image into which to encode in-place.</param>
|
||||
/// <param name="image">Image to be encoded.</param>
|
||||
/// <param name="encoder">Encoder to use.</param>
|
||||
public static void EncodeFrom(EncodedImage encodedImage, Image image, BitmapEncoder encoder)
|
||||
{
|
||||
System.Windows.Media.PixelFormat format;
|
||||
if (image.PixelFormat == PixelFormat.BGR_24bpp)
|
||||
{
|
||||
format = System.Windows.Media.PixelFormats.Bgr24;
|
||||
}
|
||||
else if (image.PixelFormat == PixelFormat.Gray_16bpp)
|
||||
{
|
||||
format = System.Windows.Media.PixelFormats.Gray16;
|
||||
}
|
||||
else if (image.PixelFormat == PixelFormat.Gray_8bpp)
|
||||
{
|
||||
format = System.Windows.Media.PixelFormats.Gray8;
|
||||
}
|
||||
else
|
||||
{
|
||||
format = System.Windows.Media.PixelFormats.Bgr32;
|
||||
}
|
||||
|
||||
encodedImage.EncodeFrom(image, (_, stream) =>
|
||||
{
|
||||
BitmapSource bitmapSource = BitmapSource.Create(image.Width, image.Height, 96, 96, format, null, image.ImageData, image.Stride * image.Height, image.Stride);
|
||||
encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
|
||||
encoder.Save(stream);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pipeline callback function for encoding an image sample.
|
||||
/// </summary>
|
||||
/// <param name="sharedImage">Image to be encoded.</param>
|
||||
/// <param name="e">Pipeline information about the sample.</param>
|
||||
protected override void Receive(Shared<Image> sharedImage, Envelope e)
|
||||
{
|
||||
// the encoder has thread affinity, so we need to re-create it (we can't dispatch the call since we sdon't know if the thread that created us is pumping messages)
|
||||
var encoder = this.encoderFn();
|
||||
|
||||
using (var sharedEncodedImage = EncodedImagePool.GetOrCreate())
|
||||
{
|
||||
EncodeFrom(sharedEncodedImage.Resource, sharedImage.Resource, encoder);
|
||||
this.Out.Post(sharedEncodedImage, e.OriginatingTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ namespace Microsoft.Psi.Imaging
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using SkiaSharp;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Defines an encoded image.
|
||||
|
@ -36,7 +36,6 @@ namespace Microsoft.Psi.Imaging
|
|||
this.stream = new MemoryStream();
|
||||
this.stream.Write(contents, 0, contents.Length);
|
||||
this.stream.Position = 0;
|
||||
this.CountBytes = contents.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -49,11 +48,6 @@ namespace Microsoft.Psi.Imaging
|
|||
/// </summary>
|
||||
public int Height { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets number of bytes of data in the image.
|
||||
/// </summary>
|
||||
public int CountBytes { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Releases the image.
|
||||
/// </summary>
|
||||
|
@ -63,6 +57,16 @@ namespace Microsoft.Psi.Imaging
|
|||
this.stream = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the image data as stream.
|
||||
/// </summary>
|
||||
/// <returns>Stream containing the image data.</returns>
|
||||
public Stream GetStream()
|
||||
{
|
||||
this.stream.Position = 0;
|
||||
return this.stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the image data as a byte array.
|
||||
/// </summary>
|
||||
|
@ -72,43 +76,16 @@ namespace Microsoft.Psi.Imaging
|
|||
return this.stream.GetBuffer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the image data as a byte array.
|
||||
/// </summary>
|
||||
/// <param name="newLength">Number of bytes to return.</param>/>
|
||||
/// <returns>Byte array containing the image data.</returns>
|
||||
public byte[] GetBuffer(int newLength)
|
||||
{
|
||||
this.stream.SetLength(newLength);
|
||||
return this.stream.GetBuffer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compresses an image using the specified encoder.
|
||||
/// </summary>
|
||||
/// <param name="image">Image to compress.</param>
|
||||
/// <param name="encoder">Encoder to use to compress.</param>
|
||||
public void EncodeFrom(Image image, IBitmapEncoder encoder)
|
||||
/// <param name="encodeFn">Encoder to use to compress.</param>
|
||||
public void EncodeFrom(Image image, Action<Image, Stream> encodeFn)
|
||||
{
|
||||
this.stream.Position = 0;
|
||||
encoder.Encode(image, this.stream);
|
||||
this.stream.Flush();
|
||||
encodeFn(image, this.GetStream());
|
||||
this.Width = image.Width;
|
||||
this.Height = image.Height;
|
||||
this.CountBytes = (int)this.stream.Position;
|
||||
this.stream.Position = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses the current image into another another image.
|
||||
/// </summary>
|
||||
/// <param name="image">Image used to store decompressed results.</param>
|
||||
public void DecodeTo(Image image)
|
||||
{
|
||||
this.stream.Position = 0;
|
||||
var bmp = SKBitmap.Decode(this.stream);
|
||||
Marshal.Copy(bmp.Bytes, 0, image.ImageData, bmp.ByteCount);
|
||||
this.stream.Position = 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,7 +30,7 @@ namespace Test.Psi.Imaging
|
|||
// Test that the pipeline's operator Crop() works on a stream of images and random rectangles
|
||||
using (var pipeline = Pipeline.Create("CropViaOperator"))
|
||||
{
|
||||
var generator = Generators.Sequence(pipeline, 1, x => x + 1, 100);
|
||||
var generator = Generators.Sequence(pipeline, 1, x => x + 1, 100, TimeSpan.FromTicks(1));
|
||||
var p = Microsoft.Psi.Operators.Process<int, (Shared<Image>, System.Drawing.Rectangle)>(
|
||||
generator,
|
||||
(d, e, s) =>
|
||||
|
@ -84,7 +84,7 @@ namespace Test.Psi.Imaging
|
|||
},
|
||||
100,
|
||||
TimeSpan.FromMilliseconds(1));
|
||||
images.Join(rects, Match.Best<System.Drawing.Rectangle>()).Crop();
|
||||
images.Join(rects, Reproducible.Nearest<System.Drawing.Rectangle>()).Crop();
|
||||
pipeline.Run();
|
||||
}
|
||||
}
|
||||
|
@ -139,9 +139,9 @@ namespace Test.Psi.Imaging
|
|||
{
|
||||
// Crop a slightly different interior region of the same size and verify that the data is different (as a sanity check)
|
||||
EncodedImage encImg = new EncodedImage();
|
||||
encImg.EncodeFrom(this.testImage, new PngBitmapEncoder());
|
||||
ImageEncoder.EncodeFrom(encImg, this.testImage, new PngBitmapEncoder());
|
||||
Image target = new Image(this.testImage.Width, this.testImage.Height, this.testImage.Stride, this.testImage.PixelFormat);
|
||||
encImg.DecodeTo(target);
|
||||
ImageDecoder.DecodeTo(encImg, target);
|
||||
this.AssertAreImagesEqual(this.testImage, target);
|
||||
}
|
||||
|
||||
|
@ -151,11 +151,11 @@ namespace Test.Psi.Imaging
|
|||
{
|
||||
// Crop a slightly different interior region of the same size and verify that the data is different (as a sanity check)
|
||||
EncodedImage encImg = new EncodedImage();
|
||||
encImg.EncodeFrom(this.testImage, new JpegBitmapEncoder());
|
||||
ImageEncoder.EncodeFrom(encImg, this.testImage, new JpegBitmapEncoder());
|
||||
Image target = new Image(this.testImage.Width, this.testImage.Height, this.testImage.Stride, this.testImage.PixelFormat);
|
||||
encImg.DecodeTo(target);
|
||||
ImageDecoder.DecodeTo(encImg, target);
|
||||
Image target2 = new Image(this.testImage.Width, this.testImage.Height, this.testImage.Stride, this.testImage.PixelFormat);
|
||||
encImg.DecodeTo(target2);
|
||||
ImageDecoder.DecodeTo(encImg, target2);
|
||||
this.AssertAreImagesEqual(target, target2);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,6 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation. All rights reserved.")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("191df615-3d8f-45a3-b763-dd4a604a712a")]
|
||||
[assembly: AssemblyVersion("0.9.6.1")]
|
||||
[assembly: AssemblyFileVersion("0.9.6.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.9.6.1-beta")]
|
||||
[assembly: AssemblyVersion("0.10.16.1")]
|
||||
[assembly: AssemblyFileVersion("0.10.16.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.10.16.1-beta")]
|
||||
|
|
|
@ -10,6 +10,6 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation. All rights reserved.")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("2ae339cd-1d81-46f4-aa19-703eb0cf86fb")]
|
||||
[assembly: AssemblyVersion("0.9.6.1")]
|
||||
[assembly: AssemblyFileVersion("0.9.6.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.9.6.1-beta")]
|
||||
[assembly: AssemblyVersion("0.10.16.1")]
|
||||
[assembly: AssemblyFileVersion("0.10.16.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.10.16.1-beta")]
|
||||
|
|
|
@ -1,55 +1,59 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="CheckVariable">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Description>Provides Windows-specific components for the using the Microsoft.Speech recognition platform.</Description>
|
||||
<RootNamespace>Microsoft.Psi.MicrosoftSpeech</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DocumentationFile>bin\Release\net47\Microsoft.Psi.MicrosoftSpeech.Windows.xml</DocumentationFile>
|
||||
<CodeAnalysisRuleSet>..\..\..\..\Build\Microsoft.Psi.ruleset</CodeAnalysisRuleSet>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DocumentationFile>bin\Debug\net47\Microsoft.Psi.MicrosoftSpeech.Windows.xml</DocumentationFile>
|
||||
<CodeAnalysisRuleSet>..\..\..\..\Build\Microsoft.Psi.ruleset</CodeAnalysisRuleSet>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="stylecop.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="stylecop.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath="README.md" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Audio\Microsoft.Psi.Audio\Microsoft.Psi.Audio.csproj" />
|
||||
<ProjectReference Include="..\..\..\Language\Microsoft.Psi.Language\Microsoft.Psi.Language.csproj" />
|
||||
<ProjectReference Include="..\..\..\Runtime\Microsoft.Psi\Microsoft.Psi.csproj" />
|
||||
<ProjectReference Include="..\..\..\Speech\Microsoft.Psi.Speech\Microsoft.Psi.Speech.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Speech">
|
||||
<HintPath>$(MsSpeechSdkDir)\Assembly\Microsoft.Speech.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<Target Name="CheckVariable">
|
||||
<Warning Text="In order to build this sample you must define the environment variable MsSpeechSdkDir to point to your Intel RealSense SDK installation" ContinueOnError="WarnAndContinue" Condition="'$(MsSpeechSdkDir)'==''" />
|
||||
<CreateProperty Value="" Condition="'$(MsSpeechSdkDir)'==''">
|
||||
<Output TaskParameter="Value" PropertyName="BuildDependsOn" />
|
||||
</CreateProperty>
|
||||
<CreateProperty Value="false" Condition="'$(MsSpeechSdkDir)'==''">
|
||||
<Output TaskParameter="Value" PropertyName="GeneratePackageOnBuild" />
|
||||
</CreateProperty>
|
||||
<CreateProperty Value="" Condition="'$(MsSpeechSdkDir)'==''">
|
||||
<Output TaskParameter="Value" PropertyName="GenerateNuspecDependsOn" />
|
||||
</CreateProperty>
|
||||
</Target>
|
||||
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="CheckVariable">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Description>Provides Windows-specific components for the using the Microsoft.Speech recognition platform.</Description>
|
||||
<RootNamespace>Microsoft.Psi.MicrosoftSpeech</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DocumentationFile>bin\Release\net47\Microsoft.Psi.MicrosoftSpeech.Windows.xml</DocumentationFile>
|
||||
<CodeAnalysisRuleSet>..\..\..\..\Build\Microsoft.Psi.ruleset</CodeAnalysisRuleSet>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DocumentationFile>bin\Debug\net47\Microsoft.Psi.MicrosoftSpeech.Windows.xml</DocumentationFile>
|
||||
<CodeAnalysisRuleSet>..\..\..\..\Build\Microsoft.Psi.ruleset</CodeAnalysisRuleSet>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="stylecop.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="stylecop.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath="README.md" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Audio\Microsoft.Psi.Audio\Microsoft.Psi.Audio.csproj" />
|
||||
<ProjectReference Include="..\..\..\Language\Microsoft.Psi.Language\Microsoft.Psi.Language.csproj" />
|
||||
<ProjectReference Include="..\..\..\Runtime\Microsoft.Psi\Microsoft.Psi.csproj" />
|
||||
<ProjectReference Include="..\..\..\Speech\Microsoft.Psi.Speech\Microsoft.Psi.Speech.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Speech">
|
||||
<HintPath>$(MsSpeechSdkDir)\Assembly\Microsoft.Speech.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<Target Name="CheckVariable">
|
||||
<Warning Text="In order to build this sample you must define the environment variable MsSpeechSdkDir to point to your Microsoft.Speech installation" ContinueOnError="WarnAndContinue" Condition="'$(MsSpeechSdkDir)'==''" />
|
||||
<CreateProperty Value="" Condition="'$(MsSpeechSdkDir)'==''">
|
||||
<Output TaskParameter="Value" PropertyName="BuildDependsOn" />
|
||||
</CreateProperty>
|
||||
<CreateProperty Value="false" Condition="'$(MsSpeechSdkDir)'==''">
|
||||
<Output TaskParameter="Value" PropertyName="GeneratePackageOnBuild" />
|
||||
</CreateProperty>
|
||||
<CreateProperty Value="" Condition="'$(MsSpeechSdkDir)'==''">
|
||||
<Output TaskParameter="Value" PropertyName="GenerateNuspecDependsOn" />
|
||||
</CreateProperty>
|
||||
<CallTarget Targets="DoBuild" Condition=" ('$(MsSpeechSdkDir)' != '') AND ('$(AGENT_BUILDDIRECTORY)'=='') " />
|
||||
</Target>
|
||||
<Target Name="DoBuild" DependsOnTargets="Build">
|
||||
<Exec Command="echo Built sample using MsSpeechSdkDir='%MsSpeechSdkDir%'" />
|
||||
</Target>
|
||||
</Project>
|
|
@ -160,6 +160,50 @@ module RosMessageTypes =
|
|||
"velocity", VariableArrayVal (Seq.map Float64Val velocity |> Seq.toList)
|
||||
"effort", VariableArrayVal (Seq.map Float64Val effort |> Seq.toList)] |> Seq.ofList
|
||||
|
||||
module Image =
|
||||
let Def = { Type = "sensor_msgs/Image"
|
||||
MD5 = ""
|
||||
Fields = ["header", StructDef Standard.Header.Def.Fields
|
||||
"height", UInt32Def
|
||||
"width", UInt32Def
|
||||
"encoding", StringDef
|
||||
"is_bigendian", BoolDef
|
||||
"step", UInt32Def
|
||||
"data", VariableArrayDef UInt8Def] }
|
||||
type Kind = { Header: Standard.Header.Kind
|
||||
Height: int
|
||||
Width: int
|
||||
Encoding: string
|
||||
IsBigEndian: bool
|
||||
Step: int
|
||||
Data: byte array }
|
||||
let FromMessage m = m |> Seq.toList |> function ["header", StructVal header
|
||||
"height", UInt32Val height
|
||||
"width", UInt32Val width
|
||||
"encoding", StringVal encoding
|
||||
"is_bigendian", BoolVal isBigEndian
|
||||
"step", UInt32Val step
|
||||
"data", VariableArrayVal data] -> { Header = Standard.Header.FromMessage header
|
||||
Height = int height
|
||||
Width = int width
|
||||
Encoding = encoding
|
||||
IsBigEndian = isBigEndian
|
||||
Step = int step
|
||||
Data = Seq.map (function UInt8Val b -> b | _ -> malformed ()) data |> Array.ofSeq } | _ -> malformed ()
|
||||
let ToMessage { Header = header
|
||||
Height = height
|
||||
Width = width
|
||||
Encoding = encoding
|
||||
IsBigEndian = isBigEndian
|
||||
Step = step
|
||||
Data = data } = ["header", StructVal (Standard.Header.ToMessage header |> Seq.toList)
|
||||
"height", UInt32Val (uint32 height)
|
||||
"width", UInt32Val (uint32 width)
|
||||
"encoding", StringVal encoding
|
||||
"is_bigendian", BoolVal isBigEndian
|
||||
"step", UInt32Val (uint32 step)
|
||||
"data", VariableArrayVal (Seq.map UInt8Val data |> List.ofSeq)] |> Seq.ofList
|
||||
|
||||
module Geometry =
|
||||
|
||||
module Point =
|
||||
|
|
|
@ -10,6 +10,6 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation. All rights reserved.")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("3a3f1c2c-a805-4ea2-b5ae-80371b565a15")]
|
||||
[assembly: AssemblyVersion("0.9.6.1")]
|
||||
[assembly: AssemblyFileVersion("0.9.6.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.9.6.1-beta")]
|
||||
[assembly: AssemblyVersion("0.10.16.1")]
|
||||
[assembly: AssemblyFileVersion("0.10.16.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.10.16.1-beta")]
|
||||
|
|
|
@ -15,6 +15,6 @@ using namespace System::Security::Permissions;
|
|||
[assembly:AssemblyCopyrightAttribute("Copyright (c) Microsoft Corporation. All rights reserved.")];
|
||||
[assembly:ComVisible(false)];
|
||||
[assembly:CLSCompliantAttribute(true)];
|
||||
[assembly:AssemblyVersionAttribute("0.9.6.1")];
|
||||
[assembly:AssemblyFileVersionAttribute("0.9.6.1")];
|
||||
[assembly:AssemblyInformationalVersionAttribute("0.9.6.1-beta")];
|
||||
[assembly:AssemblyVersionAttribute("0.10.16.1")];
|
||||
[assembly:AssemblyFileVersionAttribute("0.10.16.1")];
|
||||
[assembly:AssemblyInformationalVersionAttribute("0.10.16.1-beta")];
|
||||
|
|
Двоичный файл не отображается.
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" InitialTargets="CheckVariable">
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
|
@ -77,16 +77,13 @@
|
|||
<AdditionalFiles Include="stylecop.json" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="OldBuild" DependsOnTargets="$(OldBuildDependsOn)"/>
|
||||
<Target Name="CheckVariable">
|
||||
<Warning Text="In order to build this sample you must define the environment variable RealSenseSDKDir to point to your Intel RealSense SDK installation" ContinueOnError="WarnAndContinue" Condition="'$(RealSenseSDKDir)'==''" />
|
||||
<CreateProperty Value="" Condition="'$(RealSenseSDKDir)'==''">
|
||||
<Output TaskParameter="Value" PropertyName="BuildDependsOn" />
|
||||
</CreateProperty>
|
||||
<CreateProperty Value="false" Condition="'$(RealSenseSDKDir)'==''">
|
||||
<Output TaskParameter="Value" PropertyName="GeneratePackageOnBuild" />
|
||||
</CreateProperty>
|
||||
<CreateProperty Value="" Condition="'$(RealSenseSDKDir)'==''">
|
||||
<Output TaskParameter="Value" PropertyName="GenerateNuspecDependsOn" />
|
||||
</CreateProperty>
|
||||
<CallTarget Targets="OldBuild" Condition="'$(RealSenseSDKDir)'!=''"/>
|
||||
</Target>
|
||||
</Project>
|
||||
<PropertyGroup>
|
||||
<OldBuildDependsOn>$(BuildDependsOn)</OldBuildDependsOn>
|
||||
<BuildDependsOn>CheckVariable</BuildDependsOn>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
@ -35,6 +35,6 @@ using System.Runtime.InteropServices;
|
|||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("0.9.6.1")]
|
||||
[assembly: AssemblyFileVersion("0.9.6.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.9.6.1-beta")]
|
||||
[assembly: AssemblyVersion("0.10.16.1")]
|
||||
[assembly: AssemblyFileVersion("0.10.16.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.10.16.1-beta")]
|
||||
|
|
|
@ -31,9 +31,9 @@ using namespace System::Security::Permissions;
|
|||
// You can specify all the value or you can default the Revision and Build Numbers
|
||||
// by using the '*' as shown below:
|
||||
|
||||
[assembly:AssemblyVersionAttribute("0.9.6.1")];
|
||||
[assembly:AssemblyFileVersionAttribute("0.9.6.1")];
|
||||
[assembly:AssemblyInformationalVersionAttribute("0.9.6.1-beta")];
|
||||
[assembly:AssemblyVersionAttribute("0.10.16.1")];
|
||||
[assembly:AssemblyFileVersionAttribute("0.10.16.1")];
|
||||
[assembly:AssemblyInformationalVersionAttribute("0.10.16.1-beta")];
|
||||
|
||||
[assembly:ComVisible(false)];
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" InitialTargets="CheckVariable">
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
|
@ -126,16 +126,13 @@
|
|||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
<Target Name="OldBuild" DependsOnTargets="$(OldBuildDependsOn)"/>
|
||||
<Target Name="CheckVariable">
|
||||
<Warning Text="In order to build this sample you must define the environment variable RealSenseSDKDir to point to your Intel RealSense SDK installation" ContinueOnError="WarnAndContinue" Condition="'$(RealSenseSDKDir)'==''" />
|
||||
<CreateProperty Value="" Condition="'$(RealSenseSDKDir)'==''">
|
||||
<Output TaskParameter="Value" PropertyName="BuildDependsOn" />
|
||||
</CreateProperty>
|
||||
<CreateProperty Value="false" Condition="'$(RealSenseSDKDir)'==''">
|
||||
<Output TaskParameter="Value" PropertyName="GeneratePackageOnBuild" />
|
||||
</CreateProperty>
|
||||
<CreateProperty Value="" Condition="'$(RealSenseSDKDir)'==''">
|
||||
<Output TaskParameter="Value" PropertyName="GenerateNuspecDependsOn" />
|
||||
</CreateProperty>
|
||||
<CallTarget Targets="OldBuild" Condition="'$(RealSenseSDKDir)'!=''"/>
|
||||
</Target>
|
||||
</Project>
|
||||
<PropertyGroup>
|
||||
<OldBuildDependsOn>$(BuildDependsOn)</OldBuildDependsOn>
|
||||
<BuildDependsOn>CheckVariable</BuildDependsOn>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
@ -75,40 +75,82 @@ A single `CsvFormat` class provides implementations for comma-separated-values e
|
|||
|
||||
The first column is the originating time of the messages and is named as such.
|
||||
|
||||
There are several limitations to the CSV format. Much of the type information is lost and hierarchical values and collection properties not (currently) allowed - such properties are merely skipped. For example a face tracker message type containing an `ID`, a `Confidence` and a `Face` property, each with a `Rect` having an `X` and `Y` location and `Width`/`Height`, would only serialize the root primitive properties; in this case only the `ID` and `Confidence` (along with originating time):
|
||||
Each message includes the header row. Deserializing these will give messages represented as an `ExpandoObject` with named `dynamic` properties for each column (except `_OriginatingTime_`).
|
||||
|
||||
There are several limitations to the CSV format. Much of the type information is lost, hierarchical values are flattened and collection properties are ignored.
|
||||
For example a face tracker message of the following form would serialize the leaf primitive properties as peers:
|
||||
|
||||
```
|
||||
new
|
||||
{
|
||||
ID = 123,
|
||||
Confidence = 0.92,
|
||||
Face = new // child properties of Face are serialized as siblings to ID and Confidence
|
||||
{
|
||||
X = 213,
|
||||
Y = 107,
|
||||
Width = 42,
|
||||
Height = 61
|
||||
Points = new [] { 123, 456 } // collection property ignored
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Serializes to:
|
||||
|
||||
```csv
|
||||
_OriginatingTime_,ID,Confidence
|
||||
2018-09-06T00:39:19.0883965Z,123,0.92
|
||||
2018-09-06T00:39:19.0983965Z,123,0.89
|
||||
_OriginatingTime_,ID,Confidence,X,Y,Width,Height
|
||||
2018-09-06T00:39:19.0883965Z,123,0.92,213,107,42,61
|
||||
...
|
||||
```
|
||||
|
||||
Given a stream of face tracker results, it is easy enough to select out a "flattened" view:
|
||||
Identically named fields at different levels in the hierarchy can become confusing. For example, if each `Face` also has a `Confidence`:
|
||||
|
||||
```
|
||||
new
|
||||
{
|
||||
ID = 123,
|
||||
Confidence = 0.92,
|
||||
Face = new
|
||||
{
|
||||
Confidence = 0.89, // additional Confidence property
|
||||
X = 213,
|
||||
Y = 107,
|
||||
Width = 42,
|
||||
Height = 61
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
This serializes _both_ properties as adjacent columns, sharing the name "Confidence" with the outer field first:
|
||||
|
||||
```csv
|
||||
_OriginatingTime_,ID,Confidence,Confidence,X,Y,Width,Height
|
||||
2018-09-06T00:39:19.0883965Z,123,0.92,0.89,213,107,42,61
|
||||
...
|
||||
```
|
||||
|
||||
While serialization to this ill-advised schema will work, _deserializing_ from this CSV data will fail to construct `ExpandoObjects` because of the identically named fields.
|
||||
|
||||
A solution is to `Select(...)` a more sane structure. Given a stream of face tracker results, it is easy enough to select out a "flattened" view with clear names of your choice (e.g. "OverallConfidence" vs. "FaceConfidence"):
|
||||
|
||||
```csharp
|
||||
var flattened = faces.Select(f => new { ID = f.ID,
|
||||
Confidence = f.Confidence,
|
||||
FaceX=f.Rect.X,
|
||||
FaceY=f.Rect.Y,
|
||||
FaceWidth=f.Rect.Width,
|
||||
FaceHeight=f.Rect.Height });
|
||||
OverallConfidence = f.Confidence, // overall confidence
|
||||
FaceConfidence = f.Face.Confidence, // face confidence
|
||||
FaceX=f.Face.Rect.X,
|
||||
FaceY=f.Face.Rect.Y,
|
||||
FaceWidth=f.Face.Rect.Width,
|
||||
FaceHeight=f.Face.Rect.Height });
|
||||
```
|
||||
|
||||
Serializing this would then produce a stream of messages in the form:
|
||||
|
||||
```csv
|
||||
_OriginatingTime_,ID,Confidence,FaceX,FaceY,FaceWidth,FaceHeight
|
||||
2018-09-06T00:39:19.0883965Z,123,0.92,213,107,42,61
|
||||
_OriginatingTime_,ID,OverallConfidence,FaceConfidence,FaceX,FaceY,FaceWidth,FaceHeight
|
||||
2018-09-06T00:39:19.0883965Z,123,0.92,0.89,213,107,42,61
|
||||
```
|
||||
|
||||
```csv
|
||||
_OriginatingTime_,ID,Confidence,FaceX,FaceY,FaceWidth,FaceHeight
|
||||
2018-09-06T00:39:19.0983965Z,123,0.89,215,101,44,63
|
||||
```
|
||||
|
||||
Notice that each message includes the header row. Deserializing these will give messages represented as an `ExpandoObject` with named `dynamic` properties for each column (except `_OriginatingTime_`).
|
||||
|
||||
### Simple Primitives
|
||||
|
||||
In the case of a simple stream of primitive types (e.g. `double` with `faces.Select(f => f.Confidence)`), the persisted form looks like:
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Common.Interpolators
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Implements a interpolator based on the values adjacent to the interpolation time, i.e. the
|
||||
/// nearest values before and after the interpolation time.
|
||||
/// </summary>
|
||||
/// <typeparam name="TIn">The type of the messages to interpolate.</typeparam>
|
||||
/// <typeparam name="TOut">The type of the output interpolation result.</typeparam>
|
||||
/// <remarks>The interpolator results do not depend on the wall-clock time of the messages arriving
|
||||
/// on the secondary stream, i.e., they are based on originating times of messages. As a result,
|
||||
/// the interpolator might introduce an extra delay as it might have to wait for enough messages on the
|
||||
/// secondary stream to proove that the interpolation result is correct, irrespective of any other messages
|
||||
/// that might arrive later.</remarks>
|
||||
public class AdjacentValuesInterpolator<TIn, TOut> : ReproducibleInterpolator<TIn, TOut>
|
||||
{
|
||||
private readonly Func<TIn, TIn, double, TOut> interpolator;
|
||||
private readonly bool orDefault;
|
||||
private readonly TOut defaultValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AdjacentValuesInterpolator{TIn, TOut}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="interpolator">An interpolator function which given the two nearest values and the ratio
|
||||
/// between them where the interpolation result should be produces the interpolation result.</param>
|
||||
/// <param name="orDefault">Indicates whether to output a default value when no result is found.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
public AdjacentValuesInterpolator(Func<TIn, TIn, double, TOut> interpolator, bool orDefault, TOut defaultValue = default)
|
||||
{
|
||||
this.interpolator = interpolator;
|
||||
this.orDefault = orDefault;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override InterpolationResult<TOut> Interpolate(DateTime interpolationTime, IEnumerable<Message<TIn>> messages, DateTime? closedOriginatingTime)
|
||||
{
|
||||
// If no messages available,
|
||||
if (messages.Count() == 0)
|
||||
{
|
||||
// If stream is closed,
|
||||
if (closedOriginatingTime.HasValue)
|
||||
{
|
||||
// Then no other value or better match will appear, so depending on orDefault,
|
||||
// either create a default value or return does not exist.
|
||||
return this.orDefault ?
|
||||
InterpolationResult<TOut>.Create(this.defaultValue, DateTime.MinValue) :
|
||||
InterpolationResult<TOut>.DoesNotExist(DateTime.MinValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise if the stream is not closed yet, insufficient data
|
||||
return InterpolationResult<TOut>.InsufficientData();
|
||||
}
|
||||
}
|
||||
|
||||
Message<TIn> lastMessage = default;
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
// If we have a message past or at the interpolation time
|
||||
if (message.OriginatingTime >= interpolationTime)
|
||||
{
|
||||
// Then if we have a previous message
|
||||
if (lastMessage != default)
|
||||
{
|
||||
// Then interpolate and return the result
|
||||
var ratio = (interpolationTime - lastMessage.OriginatingTime).Ticks / (double)(message.OriginatingTime - lastMessage.OriginatingTime).Ticks;
|
||||
return InterpolationResult<TOut>.Create(
|
||||
this.interpolator(lastMessage.Data, message.Data, ratio),
|
||||
message.OriginatingTime == interpolationTime ? message.OriginatingTime : lastMessage.OriginatingTime);
|
||||
}
|
||||
else if (message.OriginatingTime == interpolationTime)
|
||||
{
|
||||
// O/w if the message is right at the interpolation time, we don't need the previous
|
||||
// message
|
||||
return InterpolationResult<TOut>.Create(this.interpolator(default, message.Data, 1), message.OriginatingTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
// o/w there since we have no previous message, depending on orDefault,
|
||||
// either create a default value or return does not exist.
|
||||
return this.orDefault ?
|
||||
InterpolationResult<TOut>.Create(this.defaultValue, DateTime.MinValue) :
|
||||
InterpolationResult<TOut>.DoesNotExist(DateTime.MinValue);
|
||||
}
|
||||
}
|
||||
|
||||
lastMessage = message;
|
||||
}
|
||||
|
||||
// If we are here, that means we have not seen enough data to create an interpolation result.
|
||||
// If the stream has closed
|
||||
if (closedOriginatingTime.HasValue)
|
||||
{
|
||||
// Then we will never get enough data to interpolate, so depending on orDefault
|
||||
// either create a default value or return does not exist.
|
||||
return this.orDefault ?
|
||||
InterpolationResult<TOut>.Create(this.defaultValue, DateTime.MinValue) :
|
||||
InterpolationResult<TOut>.DoesNotExist(lastMessage != default ? lastMessage.OriginatingTime : DateTime.MinValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
// O/w we might get more data so simply wait
|
||||
return InterpolationResult<TOut>.InsufficientData();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi
|
||||
{
|
||||
using System;
|
||||
using Microsoft.Psi.Common.Interpolators;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of greedy interpolators that act on immediately available data.
|
||||
/// </summary>
|
||||
/// <remarks>The interpolators defined by the <see cref="Available"/> class produce results
|
||||
/// based on what is available on the secondary stream at the moment the primary message
|
||||
/// arrives. As such, they depend on the wall-clock time of message arrival, and hence are
|
||||
/// not guaranteed to produce reproducible results. For reproducible interpolators, see
|
||||
/// the interpolators defined by the <see cref="Reproducible"/> static class.</remarks>
|
||||
public static class Available
|
||||
{
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the available value with an originating time exactly matching the interpolation time.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> Exact<T>()
|
||||
{
|
||||
return new NearestAvailableInterpolator<T>(RelativeTimeInterval.Zero, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the available value with an originating time exactly matching the interpolation time,
|
||||
/// or default if no such value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> ExactOrDefault<T>(T defaultValue = default)
|
||||
{
|
||||
return new NearestAvailableInterpolator<T>(RelativeTimeInterval.Zero, true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the available value with an originating time nearest to the interpolation time.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> Nearest<T>()
|
||||
{
|
||||
return new NearestAvailableInterpolator<T>(RelativeTimeInterval.Infinite, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the available value with an originating time nearest to the interpolation time,
|
||||
/// within a specified <see cref="RelativeTimeInterval"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the nearest message.</param>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> Nearest<T>(RelativeTimeInterval relativeTimeInterval)
|
||||
{
|
||||
return new NearestAvailableInterpolator<T>(relativeTimeInterval, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the available value with an originating time nearest to the interpolation time,
|
||||
/// within a given tolerance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="tolerance">The tolerance within which to search for the nearest message.</param>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> Nearest<T>(TimeSpan tolerance)
|
||||
{
|
||||
return new NearestAvailableInterpolator<T>(new RelativeTimeInterval(-tolerance, tolerance), false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the available value with an originating time nearest to the interpolation time,
|
||||
/// or default if no such value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> NearestOrDefault<T>(T defaultValue = default)
|
||||
{
|
||||
return new NearestAvailableInterpolator<T>(RelativeTimeInterval.Infinite, true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the available value with an originating time nearest to the interpolation time,
|
||||
/// within a specified <see cref="RelativeTimeInterval"/>, or default if no such value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the nearest message.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> NearestOrDefault<T>(RelativeTimeInterval relativeTimeInterval, T defaultValue = default)
|
||||
{
|
||||
return new NearestAvailableInterpolator<T>(relativeTimeInterval, true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the available value with an originating time nearest to the interpolation time,
|
||||
/// within a given tolerance, or default if no such value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="tolerance">The tolerance within which to search for the nearest message.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> NearestOrDefault<T>(TimeSpan tolerance, T defaultValue = default)
|
||||
{
|
||||
return new NearestAvailableInterpolator<T>(new RelativeTimeInterval(-tolerance, tolerance), true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the first available value in the stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> First<T>()
|
||||
{
|
||||
return new FirstAvailableInterpolator<T>(RelativeTimeInterval.Infinite, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the first available value within a specified <see cref="RelativeTimeInterval"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the first message.</param>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> First<T>(RelativeTimeInterval relativeTimeInterval)
|
||||
{
|
||||
return new FirstAvailableInterpolator<T>(relativeTimeInterval, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the first available value within a specified time tolerance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="tolerance">The tolerance within which to search for the first message.</param>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> First<T>(TimeSpan tolerance)
|
||||
{
|
||||
return new FirstAvailableInterpolator<T>(new RelativeTimeInterval(-tolerance, tolerance), false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the first available value in the stream, or default if no such
|
||||
/// value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> FirstOrDefault<T>(T defaultValue = default)
|
||||
{
|
||||
return new FirstAvailableInterpolator<T>(RelativeTimeInterval.Infinite, true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the first available value within a specified <see cref="RelativeTimeInterval"/>,
|
||||
/// or default if no such value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the first message.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> FirstOrDefault<T>(RelativeTimeInterval relativeTimeInterval, T defaultValue = default)
|
||||
{
|
||||
return new FirstAvailableInterpolator<T>(relativeTimeInterval, true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the first available value within a specified time tolerance, or default if
|
||||
/// no such value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="tolerance">The tolerance within which to search for the first message.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> FirstOrDefault<T>(TimeSpan tolerance, T defaultValue = default)
|
||||
{
|
||||
return new FirstAvailableInterpolator<T>(new RelativeTimeInterval(-tolerance, tolerance), true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the last available value in the stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> Last<T>()
|
||||
{
|
||||
return new LastAvailableInterpolator<T>(RelativeTimeInterval.Infinite, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the last available value within a specified <see cref="RelativeTimeInterval"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the last message.</param>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> Last<T>(RelativeTimeInterval relativeTimeInterval)
|
||||
{
|
||||
return new LastAvailableInterpolator<T>(relativeTimeInterval, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the first available value within a specified time tolerance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="tolerance">The tolerance within which to search for the last message.</param>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> Last<T>(TimeSpan tolerance)
|
||||
{
|
||||
return new LastAvailableInterpolator<T>(new RelativeTimeInterval(-tolerance, tolerance), false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the last available value in the stream, or default if no such
|
||||
/// value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> LastOrDefault<T>(T defaultValue = default)
|
||||
{
|
||||
return new LastAvailableInterpolator<T>(RelativeTimeInterval.Infinite, true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the last available value within a specified <see cref="RelativeTimeInterval"/>,
|
||||
/// or default if no such value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the last message.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> LastOrDefault<T>(RelativeTimeInterval relativeTimeInterval, T defaultValue = default)
|
||||
{
|
||||
return new LastAvailableInterpolator<T>(relativeTimeInterval, true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greedy interpolator that selects the first available value within a specified time tolerance,
|
||||
/// or default if no such value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="tolerance">The tolerance within which to search for the last message.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The greedy interpolator.</returns>
|
||||
public static GreedyInterpolator<T> LastOrDefault<T>(TimeSpan tolerance, T defaultValue = default)
|
||||
{
|
||||
return new LastAvailableInterpolator<T>(new RelativeTimeInterval(-tolerance, tolerance), true, defaultValue);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Common.Interpolators
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Implements a greedy interpolator that selects the first value from a specified window. The
|
||||
/// interpolator only considers messages available in the window on the secondary stream at
|
||||
/// the moment the primary stream message arrives. As such, it belongs to the class of greedy
|
||||
/// interpolators and does not guarantee reproducible results.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of messages.</typeparam>
|
||||
public sealed class FirstAvailableInterpolator<T> : GreedyInterpolator<T>
|
||||
{
|
||||
private readonly RelativeTimeInterval relativeTimeInterval;
|
||||
private readonly bool orDefault;
|
||||
private readonly T defaultValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FirstAvailableInterpolator{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the first message.</param>
|
||||
/// <param name="orDefault">Indicates whether to output a default value when no result is found.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
public FirstAvailableInterpolator(RelativeTimeInterval relativeTimeInterval, bool orDefault, T defaultValue = default)
|
||||
{
|
||||
if (!relativeTimeInterval.LeftEndpoint.Bounded)
|
||||
{
|
||||
throw new NotSupportedException($"{nameof(FirstAvailableInterpolator<T>)} does not support relative time intervals that are " +
|
||||
$"left-unbounded. The use of this interpolator in a fusion operation like Fuse or Join could lead to an incremental, " +
|
||||
$"continuous accumulation of messages on the secondary stream queue held by the fusion component. If you wish to interpolate " +
|
||||
$"by selecting the very first message in the secondary stream, please instead apply the First() operator on the " +
|
||||
$"secondary (with an unlimited delivery policy), and use the {nameof(NearestAvailableInterpolator<T>)}, as this " +
|
||||
$"will accomplish the same result.");
|
||||
}
|
||||
|
||||
this.relativeTimeInterval = relativeTimeInterval;
|
||||
this.orDefault = orDefault;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override InterpolationResult<T> Interpolate(DateTime interpolationTime, IEnumerable<Message<T>> messages, DateTime? closedOriginatingTime)
|
||||
{
|
||||
// If no messages available,
|
||||
if (messages.Count() == 0)
|
||||
{
|
||||
// Then depending on orDefault, either create a default value or return does not exist.
|
||||
return this.orDefault ?
|
||||
InterpolationResult<T>.Create(this.defaultValue, DateTime.MinValue) :
|
||||
InterpolationResult<T>.DoesNotExist(DateTime.MinValue);
|
||||
}
|
||||
|
||||
Message<T> firstMessage = default;
|
||||
bool found = false;
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
var delta = message.OriginatingTime - interpolationTime;
|
||||
|
||||
// Determine if the message is on the right side of the window start
|
||||
var messageIsAfterWindowStart = this.relativeTimeInterval.LeftEndpoint.Inclusive ? delta >= this.relativeTimeInterval.Left : delta > this.relativeTimeInterval.Left;
|
||||
|
||||
// Only consider messages that occur within the lookback window.
|
||||
if (messageIsAfterWindowStart)
|
||||
{
|
||||
// Determine if the message is outside the window end
|
||||
var messageIsOutsideWindowEnd = this.relativeTimeInterval.RightEndpoint.Inclusive ? delta > this.relativeTimeInterval.Right : delta >= this.relativeTimeInterval.Right;
|
||||
|
||||
// We stop searching either when we reach a message that is beyond the lookahead window
|
||||
if (messageIsOutsideWindowEnd)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// keep track of message and exit the loop as we found the first one
|
||||
firstMessage = message;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a first message in the window
|
||||
if (found)
|
||||
{
|
||||
// Return the matching message value as the matched result.
|
||||
// All messages before the matching message are obsolete.
|
||||
return InterpolationResult<T>.Create(firstMessage.Data, firstMessage.OriginatingTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
// o/w, that means no match was found, which implies there was no message in the
|
||||
// window. In that case, either return DoesNotExist or the default value.
|
||||
var windowLeft = interpolationTime.BoundedAdd(this.relativeTimeInterval.Left);
|
||||
return this.orDefault ?
|
||||
InterpolationResult<T>.Create(this.defaultValue, windowLeft) :
|
||||
InterpolationResult<T>.DoesNotExist(windowLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Common.Interpolators
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Implements a reproducible interpolator that selects the first value from a specified window.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of messages.</typeparam>
|
||||
/// <remarks>The interpolator results do not depend on the wall-clock time of the messages arriving
|
||||
/// on the secondary stream, i.e., they are based on originating times of messages. As a result,
|
||||
/// the interpolator might introduce an extra delay as it might have to wait for enough messages on the
|
||||
/// secondary stream to proove that the interpolation result is correct, irrespective of any other messages
|
||||
/// that might arrive later.</remarks>
|
||||
public sealed class FirstReproducibleInterpolator<T> : ReproducibleInterpolator<T>
|
||||
{
|
||||
private readonly RelativeTimeInterval relativeTimeInterval;
|
||||
private readonly bool orDefault;
|
||||
private readonly T defaultValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FirstReproducibleInterpolator{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the first message.</param>
|
||||
/// <param name="orDefault">Indicates whether to output a default value when no result is found.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
public FirstReproducibleInterpolator(RelativeTimeInterval relativeTimeInterval, bool orDefault, T defaultValue = default)
|
||||
{
|
||||
if (!relativeTimeInterval.LeftEndpoint.Bounded)
|
||||
{
|
||||
throw new NotSupportedException($"{nameof(FirstReproducibleInterpolator<T>)} does not support relative time intervals that are " +
|
||||
$"left-unbounded. The use of this interpolator in a fusion operation like Fuse or Join could lead to an incremental, " +
|
||||
$"continuous accumulation of messages on the secondary stream queue held by the fusion component. If you wish to interpolate " +
|
||||
$"by selecting the very first message in the secondary stream, please instead apply the First() operator on the " +
|
||||
$"secondary (with an unlimited delivery policy), and use the {nameof(NearestReproducibleInterpolator<T>)}, as this " +
|
||||
$"will accomplish the same result.");
|
||||
}
|
||||
|
||||
this.relativeTimeInterval = relativeTimeInterval;
|
||||
this.orDefault = orDefault;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override InterpolationResult<T> Interpolate(DateTime interpolationTime, IEnumerable<Message<T>> messages, DateTime? closedOriginatingTime)
|
||||
{
|
||||
// If no messages available,
|
||||
if (messages.Count() == 0)
|
||||
{
|
||||
// If stream is closed,
|
||||
if (closedOriginatingTime.HasValue)
|
||||
{
|
||||
// Then no other value or better match will appear, so depending on orDefault,
|
||||
// either create a default value or return does not exist.
|
||||
return this.orDefault ?
|
||||
InterpolationResult<T>.Create(this.defaultValue, DateTime.MinValue) :
|
||||
InterpolationResult<T>.DoesNotExist(DateTime.MinValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise insufficient data
|
||||
return InterpolationResult<T>.InsufficientData();
|
||||
}
|
||||
}
|
||||
|
||||
// Look for the first message in the window
|
||||
Message<T> foundMessage = default;
|
||||
bool found = false;
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
var delta = message.OriginatingTime - interpolationTime;
|
||||
|
||||
// Determine if the message is on the right side of the window start
|
||||
var messageIsAfterWindowStart = this.relativeTimeInterval.LeftEndpoint.Inclusive ? delta >= this.relativeTimeInterval.Left : delta > this.relativeTimeInterval.Left;
|
||||
|
||||
// Only consider messages that occur within the lookback window.
|
||||
if (messageIsAfterWindowStart)
|
||||
{
|
||||
// Determine if the message is outside the window end
|
||||
var messageIsOutsideWindowEnd = this.relativeTimeInterval.RightEndpoint.Inclusive ? delta > this.relativeTimeInterval.Right : delta >= this.relativeTimeInterval.Right;
|
||||
|
||||
// We stop searching when we reach a message that is beyond the window end
|
||||
if (messageIsOutsideWindowEnd)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// o/w we have found a message
|
||||
foundMessage = message;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a first message
|
||||
if (found)
|
||||
{
|
||||
// Return the matching message value as the matched result.
|
||||
// All messages strictly before the matching message are obsolete.
|
||||
return InterpolationResult<T>.Create(foundMessage.Data, foundMessage.OriginatingTime);
|
||||
}
|
||||
|
||||
// If we arrive here, it means no suitable match was found among the messages. This also
|
||||
// implies there is no message in the lookup window. If the stream is closed, or if the last
|
||||
// message is at or past the upper search bound then it is the case that no future messages
|
||||
// will match better.
|
||||
var lastMessageDelta = messages.Last().OriginatingTime - interpolationTime;
|
||||
var lastMessageIsOutsideWindowEnd = this.relativeTimeInterval.RightEndpoint.Inclusive ? lastMessageDelta > this.relativeTimeInterval.Right : lastMessageDelta >= this.relativeTimeInterval.Right;
|
||||
|
||||
// If the stream was closed or last message is at or past the upper search bound,
|
||||
if (closedOriginatingTime.HasValue || lastMessageIsOutsideWindowEnd)
|
||||
{
|
||||
// Then no future messages will match better, so return DoesNotExist or the default value
|
||||
// (according to the parameter)
|
||||
var windowLeftDateTime = interpolationTime.BoundedAdd(this.relativeTimeInterval.Left);
|
||||
return this.orDefault ?
|
||||
InterpolationResult<T>.Create(this.defaultValue, windowLeftDateTime) :
|
||||
InterpolationResult<T>.DoesNotExist(windowLeftDateTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise signal insufficient data.
|
||||
return InterpolationResult<T>.InsufficientData();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a greedy stream interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TIn">The type of the input messages.</typeparam>
|
||||
/// <typeparam name="TResult">The type of the interpolation result.</typeparam>
|
||||
/// <remarks>Greedy interpolators produce results based on what is available on the secondary
|
||||
/// stream at the moment the primary message arrives. As such, they depend on the wall-clock
|
||||
/// time of message arrival, and hence are not guaranteed to produce reproducible results.</remarks>
|
||||
public abstract class GreedyInterpolator<TIn, TResult> : Interpolator<TIn, TResult>
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a greedy stream interpolator with the same input and output type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input messages.</typeparam>
|
||||
/// <remarks>Greedy interpolators produce results based on what is available on the secondary
|
||||
/// stream at the moment the primary message arrives. As such, they depend on the wall-clock
|
||||
/// time of message arrival, and hence are not guaranteed to produce reproducible results.</remarks>
|
||||
public abstract class GreedyInterpolator<T> : GreedyInterpolator<T, T>
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Type of interpolation result.
|
||||
/// </summary>
|
||||
public enum InterpolationResultType
|
||||
{
|
||||
/// <summary>
|
||||
/// No interpolation exists.
|
||||
/// </summary>
|
||||
DoesNotExist,
|
||||
|
||||
/// <summary>
|
||||
/// An interpolation result was created based on the data.
|
||||
/// </summary>
|
||||
Created,
|
||||
|
||||
/// <summary>
|
||||
/// No interpolation result can be created due to insufficient data.
|
||||
/// </summary>
|
||||
InsufficientData,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of interpolation.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of values being interpolated.</typeparam>
|
||||
public struct InterpolationResult<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Interpolated value (if any).
|
||||
/// </summary>
|
||||
public readonly T Value;
|
||||
|
||||
/// <summary>
|
||||
/// Type of interpolation result.
|
||||
/// </summary>
|
||||
public readonly InterpolationResultType Type;
|
||||
|
||||
/// <summary>
|
||||
/// Time prior to which messages on the interpolation stream are obsolete and can safely be discarded.
|
||||
/// </summary>
|
||||
public readonly DateTime ObsoleteTime;
|
||||
|
||||
private InterpolationResult(T value, InterpolationResultType type, DateTime obsoleteTime)
|
||||
{
|
||||
this.Value = value;
|
||||
this.Type = type;
|
||||
this.ObsoleteTime = obsoleteTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Equality comparison.
|
||||
/// </summary>
|
||||
/// <param name="first">First interpolation result.</param>
|
||||
/// <param name="second">Second interpolation result.</param>
|
||||
/// <returns>A value indicating whether the interpolation results are equal.</returns>
|
||||
public static bool operator ==(InterpolationResult<T> first, InterpolationResult<T> second)
|
||||
{
|
||||
return EqualityComparer<T>.Default.Equals(first.Value, second.Value) && first.Type == second.Type && first.ObsoleteTime == second.ObsoleteTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Non-equality comparison.
|
||||
/// </summary>
|
||||
/// <param name="first">First interpolation result.</param>
|
||||
/// <param name="second">Second interpolation result.</param>
|
||||
/// <returns>A value indicating whether the interpolation results are non-equal.</returns>
|
||||
public static bool operator !=(InterpolationResult<T> first, InterpolationResult<T> second)
|
||||
{
|
||||
return !(first == second);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct interpolation result indicating insufficient data.
|
||||
/// </summary>
|
||||
/// <returns>Interpolation result indicating insufficient data.</returns>
|
||||
public static InterpolationResult<T> InsufficientData()
|
||||
{
|
||||
return new InterpolationResult<T>(default, InterpolationResultType.InsufficientData, DateTime.MinValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct interpolation result indicating no interpolation can be constructed based on the data.
|
||||
/// </summary>
|
||||
/// <param name="obsoleteTime">Time prior to which messages on the interpolation stream are obsolete and can safely be discarded.</param>
|
||||
/// <returns>Interpolation result indicating no interpolation can be constructed based on the data.</returns>
|
||||
public static InterpolationResult<T> DoesNotExist(DateTime obsoleteTime)
|
||||
{
|
||||
return new InterpolationResult<T>(default, InterpolationResultType.DoesNotExist, obsoleteTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct interpolation result indicating an interpolation was created based on the data.
|
||||
/// </summary>
|
||||
/// <param name="value">Resulting interpolation value.</param>
|
||||
/// <param name="obsoleteTime">Time prior to which messages on the interpolation stream are obsolete and can safely be discarded.</param>
|
||||
/// <returns>Interpolation result indicating an interpolation was created based on the data.</returns>
|
||||
public static InterpolationResult<T> Create(T value, DateTime obsoleteTime)
|
||||
{
|
||||
return new InterpolationResult<T>(value, InterpolationResultType.Created, obsoleteTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Equality comparison.
|
||||
/// </summary>
|
||||
/// <param name="obj">interpolation result to which to compare.</param>
|
||||
/// <returns>A value indicating equality.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return (obj is InterpolationResult<T>) && (this == (InterpolationResult<T>)obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a hashcode for the instance.
|
||||
/// </summary>
|
||||
/// <returns>The hashcode for the instance.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.Value.GetHashCode() ^ this.Type.GetHashCode() ^ this.ObsoleteTime.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a base abstract class for stream interpolators.
|
||||
/// </summary>
|
||||
/// <typeparam name="TIn">The type of the input messages.</typeparam>
|
||||
/// <typeparam name="TResult">The type of the interpolation result.</typeparam>
|
||||
public abstract class Interpolator<TIn, TResult>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Interpolator{TIn, TOut}"/> class.
|
||||
/// </summary>
|
||||
public Interpolator()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interpolates a set of messages at a given time.
|
||||
/// </summary>
|
||||
/// <param name="interpolationTime">The time to interpolate.</param>
|
||||
/// <param name="messages">The set of messages from a stream.</param>
|
||||
/// <param name="closedOriginatingTime">An optional date-time that, when present, indicates at what time the stream was closed.</param>
|
||||
/// <returns>An interpolation result <see cref="InterpolationResult{T}"/>.</returns>
|
||||
public abstract InterpolationResult<TResult> Interpolate(DateTime interpolationTime, IEnumerable<Message<TIn>> messages, DateTime? closedOriginatingTime);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Common.Interpolators
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Implements a greedy interpolator that selects the last value from a specified window. The
|
||||
/// interpolator only considers messages available in the window on the secondary stream at
|
||||
/// the moment the primary stream message arrives. As such, it belongs to the class of greedy
|
||||
/// interpolators and does not guarantee reproducible results.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of messages.</typeparam>
|
||||
public sealed class LastAvailableInterpolator<T> : GreedyInterpolator<T>
|
||||
{
|
||||
private readonly RelativeTimeInterval relativeTimeInterval;
|
||||
private readonly bool orDefault;
|
||||
private readonly T defaultValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LastAvailableInterpolator{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the first message.</param>
|
||||
/// <param name="orDefault">Indicates whether to output a default value when no result is found.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
public LastAvailableInterpolator(RelativeTimeInterval relativeTimeInterval, bool orDefault, T defaultValue = default)
|
||||
{
|
||||
this.relativeTimeInterval = relativeTimeInterval;
|
||||
this.orDefault = orDefault;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override InterpolationResult<T> Interpolate(DateTime interpolationTime, IEnumerable<Message<T>> messages, DateTime? closedOriginatingTime)
|
||||
{
|
||||
// If no messages available,
|
||||
if (messages.Count() == 0)
|
||||
{
|
||||
// Then depending on orDefault, either create a default value or return does not exist.
|
||||
return this.orDefault ?
|
||||
InterpolationResult<T>.Create(this.defaultValue, DateTime.MinValue) :
|
||||
InterpolationResult<T>.DoesNotExist(DateTime.MinValue);
|
||||
}
|
||||
|
||||
Message<T> lastMessage = default;
|
||||
bool found = false;
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
var delta = message.OriginatingTime - interpolationTime;
|
||||
|
||||
// Determine if the message is on the right side of the window start
|
||||
var messageIsAfterWindowStart = this.relativeTimeInterval.LeftEndpoint.Inclusive ? delta >= this.relativeTimeInterval.Left : delta > this.relativeTimeInterval.Left;
|
||||
|
||||
// Only consider messages that occur within the lookback window.
|
||||
if (messageIsAfterWindowStart)
|
||||
{
|
||||
// Determine if the message is outside the window end
|
||||
var messageIsOutsideWindowEnd = this.relativeTimeInterval.RightEndpoint.Inclusive ? delta > this.relativeTimeInterval.Right : delta >= this.relativeTimeInterval.Right;
|
||||
|
||||
// We stop searching either when we reach a message that is beyond the lookahead window
|
||||
if (messageIsOutsideWindowEnd)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// keep track of message and keep going
|
||||
lastMessage = message;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a last message in the window
|
||||
if (found)
|
||||
{
|
||||
// Return the matching message value as the matched result.
|
||||
// All messages before the matching message are obsolete.
|
||||
return InterpolationResult<T>.Create(lastMessage.Data, lastMessage.OriginatingTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
// o/w, that means no match was found, which implies there was no message in the
|
||||
// window. In that case, either return DoesNotExist or the default value.
|
||||
var windowLeft = interpolationTime.BoundedAdd(this.relativeTimeInterval.Left);
|
||||
return this.orDefault ?
|
||||
InterpolationResult<T>.Create(this.defaultValue, windowLeft) :
|
||||
InterpolationResult<T>.DoesNotExist(windowLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Common.Interpolators
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Implements a reproducible interpolator that selects the last value from a specified window.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of messages.</typeparam>
|
||||
/// <remarks>The interpolator results do not depend on the wall-clock time of the messages arriving
|
||||
/// on the secondary stream, i.e., they are based on originating times of messages. As a result,
|
||||
/// the interpolator might introduce an extra delay as it might have to wait for enough messages on the
|
||||
/// secondary stream to proove that the interpolation result is correct, irrespective of any other messages
|
||||
/// that might arrive later.</remarks>
|
||||
public sealed class LastReproducibleInterpolator<T> : ReproducibleInterpolator<T>
|
||||
{
|
||||
private readonly RelativeTimeInterval relativeTimeInterval;
|
||||
private readonly bool orDefault;
|
||||
private readonly T defaultValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LastReproducibleInterpolator{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the first message.</param>
|
||||
/// <param name="orDefault">Indicates whether to output a default value when no result is found.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
public LastReproducibleInterpolator(RelativeTimeInterval relativeTimeInterval, bool orDefault, T defaultValue = default)
|
||||
{
|
||||
this.relativeTimeInterval = relativeTimeInterval;
|
||||
this.orDefault = orDefault;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override InterpolationResult<T> Interpolate(DateTime interpolationTime, IEnumerable<Message<T>> messages, DateTime? closedOriginatingTime)
|
||||
{
|
||||
// If no messages available,
|
||||
if (messages.Count() == 0)
|
||||
{
|
||||
// If stream is closed,
|
||||
if (closedOriginatingTime.HasValue)
|
||||
{
|
||||
// Then no other value or better match will appear, so depending on orDefault,
|
||||
// either create a default value or return does not exist.
|
||||
return this.orDefault ?
|
||||
InterpolationResult<T>.Create(this.defaultValue, DateTime.MinValue) :
|
||||
InterpolationResult<T>.DoesNotExist(DateTime.MinValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise if the stream is not closed yet, insufficient data
|
||||
return InterpolationResult<T>.InsufficientData();
|
||||
}
|
||||
}
|
||||
|
||||
// Look for the last match that's stil within the window
|
||||
Message<T> lastMatchingMessage = default;
|
||||
bool found = false;
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
var delta = message.OriginatingTime - interpolationTime;
|
||||
|
||||
// Determine if the message is on the right side of the window start
|
||||
var messageIsAfterWindowStart = this.relativeTimeInterval.LeftEndpoint.Inclusive ? delta >= this.relativeTimeInterval.Left : delta > this.relativeTimeInterval.Left;
|
||||
|
||||
// Only consider messages that occur within the lookback window.
|
||||
if (messageIsAfterWindowStart)
|
||||
{
|
||||
// Determine if the message is outside the window end
|
||||
var messageIsOutsideWindowEnd = this.relativeTimeInterval.RightEndpoint.Inclusive ? delta > this.relativeTimeInterval.Right : delta >= this.relativeTimeInterval.Right;
|
||||
|
||||
// We stop searching when we reach a message that is beyond the window end
|
||||
if (messageIsOutsideWindowEnd)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// keep track of the best match so far and its delta
|
||||
lastMatchingMessage = message;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute whether the last available message is beyond the lookup window
|
||||
var lastMessageDelta = messages.Last().OriginatingTime - interpolationTime;
|
||||
var lastMessageIsOutsideWindowEnd = this.relativeTimeInterval.RightEndpoint.Inclusive ? lastMessageDelta > this.relativeTimeInterval.Right : lastMessageDelta >= this.relativeTimeInterval.Right;
|
||||
|
||||
// If we found a last message in the lookup window
|
||||
if (found)
|
||||
{
|
||||
// Then we need to make sure it is indeed provably the last message we will see in
|
||||
// that window. For this to be the case, either the stream has to be closed, or the
|
||||
// last matching message has to be on the right end of an inclusive window, or the
|
||||
// last message we have available must be beyond the right end of the lookup window.
|
||||
var provablyLast =
|
||||
closedOriginatingTime.HasValue ||
|
||||
(this.relativeTimeInterval.RightEndpoint.Inclusive && (lastMatchingMessage.OriginatingTime - interpolationTime == this.relativeTimeInterval.Right)) ||
|
||||
lastMessageIsOutsideWindowEnd;
|
||||
|
||||
// If we can prove this is indeed the last message
|
||||
if (provablyLast)
|
||||
{
|
||||
// Return the matching message value as the matched result.
|
||||
// All messages strictly before the matching message are obsolete.
|
||||
return InterpolationResult<T>.Create(lastMatchingMessage.Data, lastMatchingMessage.OriginatingTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
// O/w return insufficient data.
|
||||
return InterpolationResult<T>.InsufficientData();
|
||||
}
|
||||
}
|
||||
|
||||
// If we arrive here, it means we did not find a last message in the lookup window,
|
||||
// which also means there is no message in the loopkup window.
|
||||
|
||||
// If the stream was closed or last message is at or past the upper search bound,
|
||||
if (closedOriginatingTime.HasValue || lastMessageIsOutsideWindowEnd)
|
||||
{
|
||||
// Then no future messages will match better, so return DoesNotExist or the default value
|
||||
// (according to the parameter)
|
||||
var windowLeftDateTime = interpolationTime.BoundedAdd(this.relativeTimeInterval.Left);
|
||||
return this.orDefault ?
|
||||
InterpolationResult<T>.Create(this.defaultValue, windowLeftDateTime) :
|
||||
InterpolationResult<T>.DoesNotExist(windowLeftDateTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise signal insufficient data.
|
||||
return InterpolationResult<T>.InsufficientData();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Common.Interpolators
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Implements a greedy interpolator that selects the nearest value from a specified window. The
|
||||
/// interpolator only considers messages available in the window on the secondary stream at
|
||||
/// the moment the primary stream message arrives. As such, it belongs to the class of greedy
|
||||
/// interpolators and does not guarantee reproducible results.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of messages.</typeparam>
|
||||
public sealed class NearestAvailableInterpolator<T> : GreedyInterpolator<T>
|
||||
{
|
||||
private readonly RelativeTimeInterval relativeTimeInterval;
|
||||
private readonly bool orDefault;
|
||||
private readonly T defaultValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NearestAvailableInterpolator{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the first message.</param>
|
||||
/// <param name="orDefault">Indicates whether to output a default value when no result is found.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
public NearestAvailableInterpolator(RelativeTimeInterval relativeTimeInterval, bool orDefault, T defaultValue = default)
|
||||
{
|
||||
this.relativeTimeInterval = relativeTimeInterval;
|
||||
this.orDefault = orDefault;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override InterpolationResult<T> Interpolate(DateTime interpolationTime, IEnumerable<Message<T>> messages, DateTime? closedOriginatingTime)
|
||||
{
|
||||
// If no messages available,
|
||||
if (messages.Count() == 0)
|
||||
{
|
||||
// Then depending on orDefault, either create a default value or return does not exist.
|
||||
return this.orDefault ?
|
||||
InterpolationResult<T>.Create(this.defaultValue, DateTime.MinValue) :
|
||||
InterpolationResult<T>.DoesNotExist(DateTime.MinValue);
|
||||
}
|
||||
|
||||
Message<T> nearestMatch = default;
|
||||
TimeSpan minDistance = TimeSpan.MaxValue;
|
||||
DateTime upperBound = (this.relativeTimeInterval.Right < TimeSpan.Zero) ? interpolationTime.BoundedAdd(this.relativeTimeInterval.Right) : interpolationTime;
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
var delta = message.OriginatingTime - interpolationTime;
|
||||
|
||||
// Determine if the message is on the right side of the window start
|
||||
var messageIsAfterWindowStart = this.relativeTimeInterval.LeftEndpoint.Inclusive ? delta >= this.relativeTimeInterval.Left : delta > this.relativeTimeInterval.Left;
|
||||
|
||||
// Only consider messages that occur within the lookback window.
|
||||
if (messageIsAfterWindowStart)
|
||||
{
|
||||
// Determine if the message is outside the window end
|
||||
var messageIsOutsideWindowEnd = this.relativeTimeInterval.RightEndpoint.Inclusive ? delta > this.relativeTimeInterval.Right : delta >= this.relativeTimeInterval.Right;
|
||||
|
||||
// We stop searching either when we reach a message that is beyond the lookahead
|
||||
// window or when the distance (absolute delta) exceeds the best distance.
|
||||
var distance = delta.Duration();
|
||||
if (messageIsOutsideWindowEnd || (distance > minDistance))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// keep track of the nearest match so far and its delta
|
||||
nearestMatch = message;
|
||||
minDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
// If minDistance is anything other than MaxValue, we found a nearest matching message.
|
||||
if (minDistance < TimeSpan.MaxValue)
|
||||
{
|
||||
// Return the matching message value as the matched result.
|
||||
// All messages before the matching message are obsolete.
|
||||
return InterpolationResult<T>.Create(nearestMatch.Data, nearestMatch.OriginatingTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
// o/w, that means no match was found. In that case, either return
|
||||
// DoesNotExist or the default value (according to the parameter)
|
||||
return this.orDefault ?
|
||||
InterpolationResult<T>.Create(this.defaultValue, upperBound) :
|
||||
InterpolationResult<T>.DoesNotExist(upperBound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Common.Interpolators
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Implements a reproducible interpolator that selects the nearest value from a specified window.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of messages.</typeparam>
|
||||
/// <remarks>The interpolator results do not depend on the wall-clock time of the messages arriving
|
||||
/// on the secondary stream, i.e., they are based on originating times of messages. As a result,
|
||||
/// the interpolator might introduce an extra delay as it might have to wait for enough messages on the
|
||||
/// secondary stream to proove that the interpolation result is correct, irrespective of any other messages
|
||||
/// that might arrive later.</remarks>
|
||||
public sealed class NearestReproducibleInterpolator<T> : ReproducibleInterpolator<T>
|
||||
{
|
||||
private readonly RelativeTimeInterval relativeTimeInterval;
|
||||
private readonly bool orDefault;
|
||||
private readonly T defaultValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NearestReproducibleInterpolator{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the first message.</param>
|
||||
/// <param name="orDefault">Indicates whether to output a default value when no result is found.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
public NearestReproducibleInterpolator(RelativeTimeInterval relativeTimeInterval, bool orDefault, T defaultValue = default)
|
||||
{
|
||||
this.relativeTimeInterval = relativeTimeInterval;
|
||||
this.orDefault = orDefault;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override InterpolationResult<T> Interpolate(DateTime interpolationTime, IEnumerable<Message<T>> messages, DateTime? closedOriginatingTime)
|
||||
{
|
||||
var count = messages.Count();
|
||||
|
||||
// If no messages available
|
||||
if (count == 0)
|
||||
{
|
||||
// If stream is closed,
|
||||
if (closedOriginatingTime.HasValue)
|
||||
{
|
||||
// Then no other value or better match will appear, so depending on orDefault,
|
||||
// either create a default value or return does not exist.
|
||||
return this.orDefault ?
|
||||
InterpolationResult<T>.Create(this.defaultValue, DateTime.MinValue) :
|
||||
InterpolationResult<T>.DoesNotExist(DateTime.MinValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise if the stream is not closed yet, insufficient data
|
||||
return InterpolationResult<T>.InsufficientData();
|
||||
}
|
||||
}
|
||||
|
||||
// Look for the nearest match
|
||||
Message<T> nearestMatch = default;
|
||||
TimeSpan minDistance = TimeSpan.MaxValue;
|
||||
DateTime upperBound = (this.relativeTimeInterval.Right < TimeSpan.Zero) ? interpolationTime.BoundedAdd(this.relativeTimeInterval.Right) : interpolationTime;
|
||||
|
||||
int i = 0;
|
||||
foreach (var message in messages)
|
||||
{
|
||||
var delta = message.OriginatingTime - interpolationTime;
|
||||
|
||||
// Determine if the message is on the right side of the window start
|
||||
var messageIsAfterWindowStart = this.relativeTimeInterval.LeftEndpoint.Inclusive ? delta >= this.relativeTimeInterval.Left : delta > this.relativeTimeInterval.Left;
|
||||
|
||||
// Only consider messages that occur within the lookback window.
|
||||
if (messageIsAfterWindowStart)
|
||||
{
|
||||
// Determine if the message is outside the window end
|
||||
var messageIsOutsideWindowEnd = this.relativeTimeInterval.RightEndpoint.Inclusive ? delta > this.relativeTimeInterval.Right : delta >= this.relativeTimeInterval.Right;
|
||||
|
||||
var distance = delta.Duration();
|
||||
|
||||
// We stop searching either when we reach a message that is beyond the window end
|
||||
// when the distance (absolute delta) exceeds the minimum distance.
|
||||
if (messageIsOutsideWindowEnd || (distance > minDistance))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// keep track of the nearest match so far and its delta
|
||||
nearestMatch = message;
|
||||
minDistance = distance;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// If minDistance is anything other than MaxValue, we found a nearest matching message.
|
||||
if (minDistance < TimeSpan.MaxValue)
|
||||
{
|
||||
// Check if we need to satisfy additional conditions
|
||||
|
||||
// If the nearest match is the last available message and the stream has not closed
|
||||
if ((i == count) && !closedOriginatingTime.HasValue)
|
||||
{
|
||||
// Then other messages might arrive that might constitute an even better match.
|
||||
// We need to guarantee that nearestMatch is indeed provably the nearest match. If it has an
|
||||
// originating time that occurs at or after the interpolation time (or the
|
||||
// upper boundary of the window, whichever occurs earlier in time), then this
|
||||
// must be true as we will never see a closer match in any of the messages
|
||||
// that may arrive in the future (if the stream was closed then we know that no
|
||||
// messages may arrive in the future). However if it is before the interpolation time,
|
||||
// then we will need to see a message beyond the match/window time to
|
||||
// be sure that there is no closer match (i.e. as long as we haven't seen a
|
||||
// message at or past the match/window time, it is always possible that
|
||||
// a future message will show up with a distance that is closer to the
|
||||
// interpolation time.)
|
||||
if (nearestMatch.OriginatingTime < upperBound)
|
||||
{
|
||||
// Signal insufficient data to continue waiting for more messages.
|
||||
return InterpolationResult<T>.InsufficientData();
|
||||
}
|
||||
}
|
||||
|
||||
// Return the matching message value as the matched result.
|
||||
// All messages strictly before the matching message are obsolete.
|
||||
return InterpolationResult<T>.Create(nearestMatch.Data, nearestMatch.OriginatingTime);
|
||||
}
|
||||
|
||||
// If we arrive here, it means no suitable match was found among the messages.
|
||||
// If the stream is closed, or if the last message is at or past the upper search bound
|
||||
// then it is the case that no future messages will be closer.
|
||||
if (closedOriginatingTime.HasValue || messages.Last().OriginatingTime >= upperBound)
|
||||
{
|
||||
// Then, no matched value exists at that time, and we either return DoesNotExist or
|
||||
// the default value (according to the parameter)
|
||||
return this.orDefault ?
|
||||
InterpolationResult<T>.Create(this.defaultValue, upperBound) :
|
||||
InterpolationResult<T>.DoesNotExist(upperBound);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise a better future message might arrive, therefore we signal insufficient data.
|
||||
return InterpolationResult<T>.InsufficientData();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,274 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi
|
||||
{
|
||||
using System;
|
||||
using Microsoft.Psi.Common.Interpolators;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of reproducible interpolators.
|
||||
/// </summary>
|
||||
public static class Reproducible
|
||||
{
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the value with an originating time exactly matching the interpolation time.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> Exact<T>()
|
||||
{
|
||||
return new NearestReproducibleInterpolator<T>(RelativeTimeInterval.Zero, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the value with an originating time exactly matching the interpolation time,
|
||||
/// or default if no such value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> ExactOrDefault<T>(T defaultValue = default)
|
||||
{
|
||||
return new NearestReproducibleInterpolator<T>(RelativeTimeInterval.Zero, true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the value with an originating time nearest to the interpolation time.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> Nearest<T>()
|
||||
{
|
||||
return new NearestReproducibleInterpolator<T>(RelativeTimeInterval.Infinite, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the value with an originating time nearest to the interpolation time,
|
||||
/// within a specified <see cref="RelativeTimeInterval"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the nearest message.</param>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> Nearest<T>(RelativeTimeInterval relativeTimeInterval)
|
||||
{
|
||||
return new NearestReproducibleInterpolator<T>(relativeTimeInterval, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the value with an originating time nearest to the interpolation time,
|
||||
/// within a given tolerance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="tolerance">The tolerance within which to search for the nearest message.</param>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> Nearest<T>(TimeSpan tolerance)
|
||||
{
|
||||
return new NearestReproducibleInterpolator<T>(new RelativeTimeInterval(-tolerance, tolerance), false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the value with an originating time nearest to the interpolation time,
|
||||
/// or default if no such value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> NearestOrDefault<T>(T defaultValue = default)
|
||||
{
|
||||
return new NearestReproducibleInterpolator<T>(RelativeTimeInterval.Infinite, true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the value with an originating time nearest to the interpolation time,
|
||||
/// within a specified <see cref="RelativeTimeInterval"/>, or default if no such value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the nearest message.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> NearestOrDefault<T>(RelativeTimeInterval relativeTimeInterval, T defaultValue = default)
|
||||
{
|
||||
return new NearestReproducibleInterpolator<T>(relativeTimeInterval, true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the value with an originating time nearest to the interpolation time,
|
||||
/// within a given tolerance, or default if no such value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="tolerance">The tolerance within which to search for the nearest message.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> NearestOrDefault<T>(TimeSpan tolerance, T defaultValue = default)
|
||||
{
|
||||
return new NearestReproducibleInterpolator<T>(new RelativeTimeInterval(-tolerance, tolerance), true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the first value in the stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> First<T>()
|
||||
{
|
||||
return new FirstReproducibleInterpolator<T>(RelativeTimeInterval.Infinite, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the first value within a specified <see cref="RelativeTimeInterval"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the first message.</param>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> First<T>(RelativeTimeInterval relativeTimeInterval)
|
||||
{
|
||||
return new FirstReproducibleInterpolator<T>(relativeTimeInterval, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the first value within a specified time tolerance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="tolerance">The tolerance within which to search for the first message.</param>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> First<T>(TimeSpan tolerance)
|
||||
{
|
||||
return new FirstReproducibleInterpolator<T>(new RelativeTimeInterval(-tolerance, tolerance), false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the first value in the stream, or default if no such
|
||||
/// value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> FirstOrDefault<T>(T defaultValue = default)
|
||||
{
|
||||
return new FirstReproducibleInterpolator<T>(RelativeTimeInterval.Infinite, true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the first value within a specified <see cref="RelativeTimeInterval"/>,
|
||||
/// or default if no such value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the first message.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> FirstOrDefault<T>(RelativeTimeInterval relativeTimeInterval, T defaultValue = default)
|
||||
{
|
||||
return new FirstReproducibleInterpolator<T>(relativeTimeInterval, true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the first value within a specified time tolerance, or default if
|
||||
/// no such value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="tolerance">The tolerance within which to search for the first message.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> FirstOrDefault<T>(TimeSpan tolerance, T defaultValue = default)
|
||||
{
|
||||
return new FirstReproducibleInterpolator<T>(new RelativeTimeInterval(-tolerance, tolerance), true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the last value in the stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> Last<T>()
|
||||
{
|
||||
return new LastReproducibleInterpolator<T>(RelativeTimeInterval.Infinite, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the last value within a specified <see cref="RelativeTimeInterval"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the last message.</param>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> Last<T>(RelativeTimeInterval relativeTimeInterval)
|
||||
{
|
||||
return new LastReproducibleInterpolator<T>(relativeTimeInterval, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the first value within a specified time tolerance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="tolerance">The tolerance within which to search for the last message.</param>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> Last<T>(TimeSpan tolerance)
|
||||
{
|
||||
return new LastReproducibleInterpolator<T>(new RelativeTimeInterval(-tolerance, tolerance), false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the last value in the stream, or default if no such
|
||||
/// value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> LastOrDefault<T>(T defaultValue = default)
|
||||
{
|
||||
return new LastReproducibleInterpolator<T>(RelativeTimeInterval.Infinite, true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the last value within a specified <see cref="RelativeTimeInterval"/>,
|
||||
/// or default if no such value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the last message.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> LastOrDefault<T>(RelativeTimeInterval relativeTimeInterval, T defaultValue = default)
|
||||
{
|
||||
return new LastReproducibleInterpolator<T>(relativeTimeInterval, true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that selects the first value within a specified time tolerance,
|
||||
/// or default if no such value is found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="tolerance">The tolerance within which to search for the last message.</param>
|
||||
/// <param name="defaultValue">An optional default value to use.</param>
|
||||
/// <returns>The reproducible interpolator.</returns>
|
||||
public static ReproducibleInterpolator<T> LastOrDefault<T>(TimeSpan tolerance, T defaultValue = default)
|
||||
{
|
||||
return new LastReproducibleInterpolator<T>(new RelativeTimeInterval(-tolerance, tolerance), true, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reproducible interpolator that performs a linear interpolation, between
|
||||
/// the nearest messages to the originating time.
|
||||
/// </summary>
|
||||
/// <returns>The linear interpolator.</returns>
|
||||
public static AdjacentValuesInterpolator<double, double> Linear()
|
||||
{
|
||||
return new AdjacentValuesInterpolator<double, double>((i1, i2, r) => (1 - r) * i1 + r * i2, false);
|
||||
}
|
||||
|
||||
internal static DateTime BoundedAdd(this DateTime dateTime, TimeSpan timeSpan)
|
||||
{
|
||||
if (timeSpan.Ticks > (DateTime.MaxValue.Ticks - dateTime.Ticks))
|
||||
{
|
||||
return DateTime.MaxValue;
|
||||
}
|
||||
else if (timeSpan.Ticks < -dateTime.Ticks)
|
||||
{
|
||||
return DateTime.MinValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return dateTime + timeSpan;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a reproducible stream interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TIn">The type of the input messages.</typeparam>
|
||||
/// <typeparam name="TResult">The type of the interpolation result.</typeparam>
|
||||
/// <remarks>Reproducible interpolators produce results that do not depend on the wall-clock time of
|
||||
/// message arrival on a stream, i.e., they are based on originating times of messages. As a result,
|
||||
/// these interpolators might introduce extra delays as they might have to wait for enough messages on the
|
||||
/// secondary stream to proove that the interpolation result is correct, irrespective of any other messages
|
||||
/// that might arrive later.</remarks>
|
||||
public abstract class ReproducibleInterpolator<TIn, TResult> : Interpolator<TIn, TResult>
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a reproducible stream interpolator with the same input and output type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input messages and of the result.</typeparam>
|
||||
/// <remarks>Reproducible interpolators produce results that do not depend on the wall-clock time of
|
||||
/// message arrival on a stream, i.e., they are based on originating times of messages. As a result,
|
||||
/// these interpolators might introduce extra delays as they might have to wait for enough messages on the
|
||||
/// secondary stream to proove that the interpolation result is correct, irrespective of any other messages
|
||||
/// that might arrive later.</remarks>
|
||||
public abstract class ReproducibleInterpolator<T> : ReproducibleInterpolator<T, T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Implicitly convert relative time intervals to the equivalent of a reproducible nearest match within that window.
|
||||
/// </summary>
|
||||
/// <param name="window">Window within which to match messages.</param>
|
||||
public static implicit operator ReproducibleInterpolator<T>(RelativeTimeInterval window)
|
||||
{
|
||||
return Reproducible.Nearest<T>(window);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert timespan to the equivalent of a reproducibla nearest match with that tolerance.
|
||||
/// </summary>
|
||||
/// <param name="tolerance">Relative window tolerance within which to match messages.</param>
|
||||
public static implicit operator ReproducibleInterpolator<T>(TimeSpan tolerance)
|
||||
{
|
||||
return Reproducible.Nearest<T>(tolerance);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,456 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Type of match result.
|
||||
/// </summary>
|
||||
public enum MatchResultType
|
||||
{
|
||||
/// <summary>
|
||||
/// No suitable match result found within data.
|
||||
/// </summary>
|
||||
DoesNotExist,
|
||||
|
||||
/// <summary>
|
||||
/// Match result found.
|
||||
/// </summary>
|
||||
Created,
|
||||
|
||||
/// <summary>
|
||||
/// No suitable match result found due to lack of data.
|
||||
/// </summary>
|
||||
InsufficientData,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of matching.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of values being matched.</typeparam>
|
||||
#pragma warning disable SA1649 // File name must match first type name
|
||||
public struct MatchResult<T>
|
||||
#pragma warning restore SA1649 // File name must match first type name
|
||||
{
|
||||
/// <summary>
|
||||
/// Matched value (if any).
|
||||
/// </summary>
|
||||
public readonly T Value;
|
||||
|
||||
/// <summary>
|
||||
/// Type of match result.
|
||||
/// </summary>
|
||||
public readonly MatchResultType Type;
|
||||
|
||||
/// <summary>
|
||||
/// Time after which this match result is obsolete.
|
||||
/// </summary>
|
||||
public readonly DateTime ObsoleteTime;
|
||||
|
||||
private MatchResult(T value, MatchResultType type, DateTime obsoleteTime)
|
||||
{
|
||||
this.Value = value;
|
||||
this.Type = type;
|
||||
this.ObsoleteTime = obsoleteTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Equality comparison.
|
||||
/// </summary>
|
||||
/// <param name="first">First match result.</param>
|
||||
/// <param name="second">Second match result.</param>
|
||||
/// <returns>A value indicating whether the match results are equal.</returns>
|
||||
public static bool operator ==(MatchResult<T> first, MatchResult<T> second)
|
||||
{
|
||||
return EqualityComparer<T>.Default.Equals(first.Value, second.Value) && first.Type == second.Type && first.ObsoleteTime == second.ObsoleteTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Non-equality comparison.
|
||||
/// </summary>
|
||||
/// <param name="first">First match result.</param>
|
||||
/// <param name="second">Second match result.</param>
|
||||
/// <returns>A value indicating whether the match results are non-equal.</returns>
|
||||
public static bool operator !=(MatchResult<T> first, MatchResult<T> second)
|
||||
{
|
||||
return !(first == second);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct match result indicating insufficient data.
|
||||
/// </summary>
|
||||
/// <param name="obsoleteTime">Time after which this match result is obsolete.</param>
|
||||
/// <returns>Match result indicating insufficient data.</returns>
|
||||
public static MatchResult<T> InsufficientData(DateTime obsoleteTime)
|
||||
{
|
||||
return new MatchResult<T>(default(T), MatchResultType.InsufficientData, obsoleteTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct match result indicating no match found within data.
|
||||
/// </summary>
|
||||
/// <param name="obsoleteTime">Time after which this match result is obsolete.</param>
|
||||
/// <returns>Match result indicating no match found within data.</returns>
|
||||
public static MatchResult<T> DoesNotExist(DateTime obsoleteTime)
|
||||
{
|
||||
return new MatchResult<T>(default(T), MatchResultType.DoesNotExist, obsoleteTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct match result indicating matching value found within data.
|
||||
/// </summary>
|
||||
/// <param name="value">Matched value.</param>
|
||||
/// <param name="obsoleteTime">Time after which this match result is obsolete.</param>
|
||||
/// <returns>Match result indicating matching value found within data.</returns>
|
||||
public static MatchResult<T> Create(T value, DateTime obsoleteTime)
|
||||
{
|
||||
return new MatchResult<T>(value, MatchResultType.Created, obsoleteTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Equality comparison.
|
||||
/// </summary>
|
||||
/// <param name="obj">Match result to which to compare.</param>
|
||||
/// <returns>A value indicating equality.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return (obj is MatchResult<T>) && (this == (MatchResult<T>)obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a hashcode for the instance.
|
||||
/// </summary>
|
||||
/// <returns>The hashcode for the instance.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.Value.GetHashCode() ^ this.Type.GetHashCode() ^ this.ObsoleteTime.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collection of interpolators used for matching message values.
|
||||
/// </summary>
|
||||
public static class Match
|
||||
{
|
||||
/// <summary>
|
||||
/// Match that takes the value with an originating time nearest to the match time exactly.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <remarks>
|
||||
/// The next value at or after the match time is required for a matched value to be created.
|
||||
/// This ensures correctness regardless of execution timing.
|
||||
/// </remarks>
|
||||
/// <returns>The interpolator for the match.</returns>
|
||||
public static Interpolator<T> Exact<T>()
|
||||
{
|
||||
return NearestValue<T>(RelativeTimeInterval.Zero, true, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Match that takes the value with an originating time nearest to the match time exactly.
|
||||
/// If no message is available matching exactly, it returns the default(T) value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <remarks>
|
||||
/// The next value at or after the match time is required for a matched value to be created.
|
||||
/// This ensures correctness regardless of execution timing.
|
||||
/// </remarks>
|
||||
/// <returns>The interpolator for the match.</returns>
|
||||
public static Interpolator<T> ExactOrDefault<T>()
|
||||
{
|
||||
return NearestValue<T>(RelativeTimeInterval.Zero, true, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Match that takes the value with an originating time nearest to the match
|
||||
/// time, within an infinite window which looks both forward and backward in time.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <remarks>
|
||||
/// The next value at or after the match time is required for a matched value to be created.
|
||||
/// This ensures correctness regardless of execution timing.
|
||||
/// </remarks>
|
||||
/// <returns>The interpolator for the match.</returns>
|
||||
public static Interpolator<T> Best<T>()
|
||||
{
|
||||
return NearestValue<T>(RelativeTimeInterval.Infinite, true, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Match that takes the value with an originating time nearest to the match
|
||||
/// time, within a window which looks both forward and backward in time.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="window">
|
||||
/// The window within which to search for the message that is closest to the match time.
|
||||
/// May extend up to TimeSpan.MinValue and TimeSpan.MaxValue relative to the match time.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// The next value at or after the match time is required for a matched value to be created.
|
||||
/// This ensures correctness regardless of execution timing.
|
||||
/// </remarks>
|
||||
/// <returns>The interpolator for the match.</returns>
|
||||
public static Interpolator<T> Best<T>(RelativeTimeInterval window)
|
||||
{
|
||||
return NearestValue<T>(window, true, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Match that takes the value with an originating time nearest to the match
|
||||
/// time, within a window which looks both forward and backward in time.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="tolerance">
|
||||
/// The tolerance within which to search for the message that is closest to the match time.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// The next value at or after the match time is required for a matched value to be created.
|
||||
/// This ensures correctness regardless of execution timing.
|
||||
/// </remarks>
|
||||
/// <returns>The interpolator for the match.</returns>
|
||||
public static Interpolator<T> Best<T>(TimeSpan tolerance)
|
||||
{
|
||||
return NearestValue<T>(new RelativeTimeInterval(-tolerance, tolerance), true, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Match that takes the value with an originating time nearest to the match
|
||||
/// time, within an infinite window which looks both forward and backward in time.
|
||||
/// If no message is available in that specified window, it returns the default(T) value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <remarks>
|
||||
/// The next value at or after the match time is required for a matched value to be created.
|
||||
/// This ensures correctness regardless of execution timing.
|
||||
/// </remarks>
|
||||
/// <returns>The interpolator for the match.</returns>
|
||||
public static Interpolator<T> BestOrDefault<T>()
|
||||
{
|
||||
return NearestValue<T>(RelativeTimeInterval.Infinite, true, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Match that takes the value with an originating time nearest to the match
|
||||
/// time, within a window which looks both forward and backward in time.
|
||||
/// If no message is available in that specified window, it returns the default(T) value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="window">
|
||||
/// The window within which to search for the message that is closest to the match time.
|
||||
/// May extend up to TimeSpan.MinValue and TimeSpan.MaxValue relative to the match time.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// The next value at or after the match time is required for a matched value to be created.
|
||||
/// This ensures correctness regardless of execution timing.
|
||||
/// </remarks>
|
||||
/// <returns>The interpolator for the match.</returns>
|
||||
public static Interpolator<T> BestOrDefault<T>(RelativeTimeInterval window)
|
||||
{
|
||||
return NearestValue<T>(window, true, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Match that takes the value with an originating time nearest to the match
|
||||
/// time, within a window which looks both forward and backward in time.
|
||||
/// If no message is available in that specified window, it returns the default(T) value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The message type.</typeparam>
|
||||
/// <param name="tolerance">
|
||||
/// The tolerance within which to search for the message that is closest to the match time.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// The next value at or after the match time is required for a matched value to be created.
|
||||
/// This ensures correctness regardless of execution timing.
|
||||
/// </remarks>
|
||||
/// <returns>The interpolator for the match.</returns>
|
||||
public static Interpolator<T> BestOrDefault<T>(TimeSpan tolerance)
|
||||
{
|
||||
return NearestValue<T>(new RelativeTimeInterval(-tolerance, tolerance), true, true);
|
||||
}
|
||||
|
||||
private static Interpolator<T> NearestValue<T>(RelativeTimeInterval window, bool requireNextValue, bool orDefault)
|
||||
{
|
||||
return new Interpolator<T>(NearestValueFn<T>(window, requireNextValue, orDefault), window, requireNextValue, orDefault);
|
||||
}
|
||||
|
||||
private static Func<DateTime, IEnumerable<Message<T>>, DateTime?, MatchResult<T>> NearestValueFn<T>(RelativeTimeInterval window, bool requireNextValue, bool orDefault)
|
||||
{
|
||||
return (DateTime matchTime, IEnumerable<Message<T>> messages, DateTime? closedOriginatingTime) =>
|
||||
{
|
||||
var count = messages.Count();
|
||||
|
||||
// If no messages available,
|
||||
if (count == 0)
|
||||
{
|
||||
// If stream is closed,
|
||||
if (closedOriginatingTime.HasValue)
|
||||
{
|
||||
// Then depending on orDefault, either create a default value or return does not exist.
|
||||
return orDefault && (matchTime <= closedOriginatingTime.Value) ?
|
||||
MatchResult<T>.Create(default(T), DateTime.MinValue) :
|
||||
MatchResult<T>.DoesNotExist(DateTime.MinValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise insufficient data
|
||||
return MatchResult<T>.InsufficientData(DateTime.MinValue);
|
||||
}
|
||||
}
|
||||
|
||||
Message<T> bestMatch = default(Message<T>);
|
||||
TimeSpan bestDistance = TimeSpan.MaxValue;
|
||||
DateTime upperBound = (window.Right < TimeSpan.Zero) ? matchTime + window.Right : matchTime;
|
||||
|
||||
int i = 0;
|
||||
foreach (var message in messages)
|
||||
{
|
||||
TimeSpan delta = message.OriginatingTime - matchTime;
|
||||
TimeSpan distance = delta.Duration();
|
||||
|
||||
// Determine if the message is on the right side of the window start
|
||||
var messageIsAfterWindowStart = (window.LeftEndpoint.Inclusive && delta >= window.Left) || (!window.LeftEndpoint.Inclusive && delta > window.Left);
|
||||
|
||||
// Only consider messages that occur within the lookback window.
|
||||
if (messageIsAfterWindowStart)
|
||||
{
|
||||
// Determine if the message is outside the window end
|
||||
var messageIsOutsideWindowEnd = (window.RightEndpoint.Inclusive && delta > window.Right) || (!window.RightEndpoint.Inclusive && delta >= window.Right);
|
||||
|
||||
// We stop searching either when we reach a message that is beyond the window end
|
||||
// or when the distance (absolute delta) exceeds the minimum distance.
|
||||
if (messageIsOutsideWindowEnd || (distance > bestDistance))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// keep track of the best match so far and its delta
|
||||
bestMatch = message;
|
||||
bestDistance = distance;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// If bestDistance is anything other than MaxValue, we found a nearest matching message.
|
||||
if (bestDistance < TimeSpan.MaxValue)
|
||||
{
|
||||
// Check if we need to satisfy additional conditions
|
||||
// if the best match is the last available message
|
||||
if (requireNextValue && (i == count) && !closedOriginatingTime.HasValue)
|
||||
{
|
||||
// We need to guarantee that bestMatch is indeed the best match. If it has an
|
||||
// originating time that occurs at or after the match time (or the
|
||||
// upper boundary of the window, whichever occurs earlier in time), then this
|
||||
// must be true as we will never see a closer match in any of the messages
|
||||
// that may arrive in the future (if the stream was closed then we know that no
|
||||
// messages may arrive in the future). However if it is before the match time,
|
||||
// then we will need to see a message beyond the match/window time to
|
||||
// be sure that there is no closer match (i.e. as long as we haven't seen a
|
||||
// message at or past the match/window time, it is always possible that
|
||||
// a future message will show up with a distance that is closer to the
|
||||
// match time.
|
||||
if (bestMatch.OriginatingTime < upperBound)
|
||||
{
|
||||
// Signal insufficient data to continue waiting for more messages.
|
||||
return MatchResult<T>.InsufficientData(DateTime.MinValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the matching message value as the matched result.
|
||||
// All messages before the matching message are obsolete.
|
||||
return MatchResult<T>.Create(bestMatch.Data, bestMatch.OriginatingTime);
|
||||
}
|
||||
|
||||
if (closedOriginatingTime.HasValue || messages.Last().OriginatingTime >= upperBound)
|
||||
{
|
||||
// If no nearest match was found and the match time occurs before or coincident with
|
||||
// the last message (or the stream was closed), then no future message will alter
|
||||
// the result and we can therefore conclude that no matched value exists at that time.
|
||||
|
||||
// In that case, either return DoesNotExist or the default value (according to the parameter)
|
||||
return orDefault ?
|
||||
MatchResult<T>.Create(default(T), upperBound) :
|
||||
MatchResult<T>.DoesNotExist(upperBound);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise signal insufficient data.
|
||||
return MatchResult<T>.InsufficientData(DateTime.MinValue);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interpolator within message windows.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of messages.</typeparam>
|
||||
public class Interpolator<T>
|
||||
{
|
||||
private readonly Func<DateTime, IEnumerable<Message<T>>, DateTime?, MatchResult<T>> matchFn;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Interpolator{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="match">Function producing match results over a message window.</param>
|
||||
/// <param name="window">Message window interval.</param>
|
||||
/// <param name="requireNextValue">Whether the next value is required as confirmation of proper match.</param>
|
||||
/// <param name="orDefault">Whether to return a default value upon failure to find a suitable match.</param>
|
||||
public Interpolator(Func<DateTime, IEnumerable<Message<T>>, DateTime?, MatchResult<T>> match, RelativeTimeInterval window, bool requireNextValue, bool orDefault)
|
||||
{
|
||||
this.Window = window;
|
||||
this.RequireNextValue = requireNextValue;
|
||||
this.OrDefault = orDefault;
|
||||
this.matchFn = match;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating the message window interval.
|
||||
/// </summary>
|
||||
public RelativeTimeInterval Window { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the next value is required as confirmation of proper match.
|
||||
/// </summary>
|
||||
public bool RequireNextValue { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to return a default value upon failure to find a suitable match.
|
||||
/// </summary>
|
||||
public bool OrDefault { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert relative time intervals to the equivalent of a `Best` match.
|
||||
/// </summary>
|
||||
/// <param name="window">Window within which to match messages.</param>
|
||||
public static implicit operator Interpolator<T>(RelativeTimeInterval window)
|
||||
{
|
||||
return NearestValue<T>(window, true, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert timespan to the equivalent of a `Best` match.
|
||||
/// </summary>
|
||||
/// <param name="tolerance">Relative window tolerance within which to match messages.</param>
|
||||
public static implicit operator Interpolator<T>(TimeSpan tolerance)
|
||||
{
|
||||
return NearestValue<T>(new RelativeTimeInterval(-tolerance, tolerance), true, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find suitable match result within a window of messages.
|
||||
/// </summary>
|
||||
/// <param name="matchTime">Time at which to match.</param>
|
||||
/// <param name="messages">Window of messages.</param>
|
||||
/// <param name="closedOriginatingTime">Time at which this stream was closed, or null if the stream is open.</param>
|
||||
/// <returns>Resulting match.</returns>
|
||||
public MatchResult<T> Match(DateTime matchTime, IEnumerable<Message<T>> messages, DateTime? closedOriginatingTime)
|
||||
{
|
||||
return this.matchFn(matchTime, messages, closedOriginatingTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Psi.Common.Interpolators;
|
||||
|
||||
/// <summary>
|
||||
/// Component that fuses multiple streams based on a specified interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">The type the messages on the primary stream.</typeparam>
|
||||
/// <typeparam name="TSecondary">The type messages on the secondary stream.</typeparam>
|
||||
/// <typeparam name="TInterpolation">The type of the interpolation result on the secondary stream.</typeparam>
|
||||
/// <typeparam name="TOut">The type of output message.</typeparam>
|
||||
public class Fuse<TPrimary, TSecondary, TInterpolation, TOut> : IProducer<TOut>
|
||||
{
|
||||
private readonly Pipeline pipeline;
|
||||
private readonly Queue<Message<TPrimary>> primaryQueue = new Queue<Message<TPrimary>>(); // to be paired
|
||||
private readonly Interpolator<TSecondary, TInterpolation> interpolator;
|
||||
private readonly Func<TPrimary, TInterpolation[], TOut> outputCreator;
|
||||
private readonly Func<TPrimary, IEnumerable<int>> secondarySelector;
|
||||
private (Queue<Message<TSecondary>> Queue, DateTime? ClosedOriginatingTime)[] secondaryQueues;
|
||||
private Receiver<TSecondary>[] inSecondaries;
|
||||
private bool[] receivedSecondary;
|
||||
private IEnumerable<int> defaultSecondarySet;
|
||||
|
||||
// temp buffers
|
||||
private TInterpolation[] lastValues;
|
||||
private InterpolationResult<TInterpolation>[] lastResults;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Fuse{TPrimary, TSecondary, TInterpolation, TOut}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Pipeline to which this component belongs.</param>
|
||||
/// <param name="interpolator">Interpolator to use when joining the streams.</param>
|
||||
/// <param name="outputCreator">Mapping function from messages to output.</param>
|
||||
/// <param name="secondaryCount">Number of secondary streams.</param>
|
||||
/// <param name="secondarySelector">Selector function mapping primary messages to a set of secondary stream indices.</param>
|
||||
public Fuse(
|
||||
Pipeline pipeline,
|
||||
Interpolator<TSecondary, TInterpolation> interpolator,
|
||||
Func<TPrimary, TInterpolation[], TOut> outputCreator,
|
||||
int secondaryCount = 1,
|
||||
Func<TPrimary, IEnumerable<int>> secondarySelector = null)
|
||||
: base()
|
||||
{
|
||||
this.pipeline = pipeline;
|
||||
this.Out = pipeline.CreateEmitter<TOut>(this, nameof(this.Out));
|
||||
this.InPrimary = pipeline.CreateReceiver<TPrimary>(this, this.ReceivePrimary, nameof(this.InPrimary));
|
||||
this.interpolator = interpolator;
|
||||
this.outputCreator = outputCreator;
|
||||
this.secondarySelector = secondarySelector;
|
||||
this.inSecondaries = new Receiver<TSecondary>[secondaryCount];
|
||||
this.receivedSecondary = new bool[secondaryCount];
|
||||
this.secondaryQueues = new ValueTuple<Queue<Message<TSecondary>>, DateTime?>[secondaryCount];
|
||||
this.lastValues = new TInterpolation[secondaryCount];
|
||||
this.lastResults = new InterpolationResult<TInterpolation>[secondaryCount];
|
||||
this.defaultSecondarySet = Enumerable.Range(0, secondaryCount);
|
||||
for (int i = 0; i < secondaryCount; i++)
|
||||
{
|
||||
this.secondaryQueues[i] = (new Queue<Message<TSecondary>>(), null);
|
||||
var id = i; // needed to make the closure below byval
|
||||
var receiver = pipeline.CreateReceiver<TSecondary>(this, (d, e) => this.ReceiveSecondary(id, d, e), "InSecondary" + i);
|
||||
receiver.Unsubscribed += closedOriginatingTime => this.SecondaryClosed(id, closedOriginatingTime);
|
||||
this.inSecondaries[i] = receiver;
|
||||
this.receivedSecondary[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Emitter<TOut> Out { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets primary input receiver.
|
||||
/// </summary>
|
||||
public Receiver<TPrimary> InPrimary { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets collection of secondary receivers.
|
||||
/// </summary>
|
||||
public IList<Receiver<TSecondary>> InSecondaries => this.inSecondaries;
|
||||
|
||||
/// <summary>
|
||||
/// Add input receiver.
|
||||
/// </summary>
|
||||
/// <returns>Receiver.</returns>
|
||||
public Receiver<TSecondary> AddInput()
|
||||
{
|
||||
// use the sync context to protect the queues from concurrent access
|
||||
var syncContext = this.Out.SyncContext;
|
||||
syncContext.Lock();
|
||||
|
||||
try
|
||||
{
|
||||
var lastIndex = this.inSecondaries.Length;
|
||||
var count = lastIndex + 1;
|
||||
Array.Resize(ref this.inSecondaries, count);
|
||||
var newInput = this.inSecondaries[lastIndex] = this.pipeline.CreateReceiver<TSecondary>(this, (d, e) => this.ReceiveSecondary(lastIndex, d, e), "InSecondary" + lastIndex);
|
||||
newInput.Unsubscribed += closedOriginatingTime => this.SecondaryClosed(lastIndex, closedOriginatingTime);
|
||||
|
||||
Array.Resize(ref this.receivedSecondary, count);
|
||||
this.receivedSecondary[count - 1] = false;
|
||||
|
||||
Array.Resize(ref this.secondaryQueues, count);
|
||||
this.secondaryQueues[lastIndex] = (new Queue<Message<TSecondary>>(), null);
|
||||
Array.Resize(ref this.lastResults, count);
|
||||
Array.Resize(ref this.lastValues, count);
|
||||
this.defaultSecondarySet = Enumerable.Range(0, count);
|
||||
return newInput;
|
||||
}
|
||||
finally
|
||||
{
|
||||
syncContext.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReceivePrimary(TPrimary message, Envelope e)
|
||||
{
|
||||
var clone = message.DeepClone(this.InPrimary.Recycler);
|
||||
this.primaryQueue.Enqueue(Message.Create(clone, e));
|
||||
this.Publish();
|
||||
}
|
||||
|
||||
private void ReceiveSecondary(int id, TSecondary message, Envelope e)
|
||||
{
|
||||
var clone = message.DeepClone(this.InSecondaries[id].Recycler);
|
||||
this.secondaryQueues[id].Queue.Enqueue(Message.Create(clone, e));
|
||||
this.Publish();
|
||||
}
|
||||
|
||||
private void SecondaryClosed(int index, DateTime closedOriginatingTime)
|
||||
{
|
||||
this.secondaryQueues[index].ClosedOriginatingTime = closedOriginatingTime;
|
||||
this.Publish();
|
||||
}
|
||||
|
||||
private void Publish()
|
||||
{
|
||||
while (this.primaryQueue.Count > 0)
|
||||
{
|
||||
var primary = this.primaryQueue.Peek();
|
||||
bool ready = true;
|
||||
var secondarySet = (this.secondarySelector != null) ? this.secondarySelector(primary.Data) : this.defaultSecondarySet;
|
||||
foreach (var secondary in secondarySet)
|
||||
{
|
||||
var secondaryQueue = this.secondaryQueues[secondary];
|
||||
var interpolationResult = this.interpolator.Interpolate(primary.OriginatingTime, secondaryQueue.Queue, secondaryQueue.ClosedOriginatingTime);
|
||||
if (interpolationResult.Type == InterpolationResultType.InsufficientData)
|
||||
{
|
||||
// we need to wait longer
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastResults[secondary] = interpolationResult;
|
||||
this.lastValues[secondary] = interpolationResult.Value;
|
||||
ready = ready && interpolationResult.Type == InterpolationResultType.Created;
|
||||
}
|
||||
|
||||
// if all secondaries have an interpolated value, publish the resulting set
|
||||
if (ready)
|
||||
{
|
||||
// publish
|
||||
var result = this.outputCreator(primary.Data, this.lastValues);
|
||||
this.Out.Post(result, primary.OriginatingTime);
|
||||
Array.Clear(this.lastValues, 0, this.lastValues.Length);
|
||||
}
|
||||
|
||||
// if we got here, all secondaries either successfully interpolated a value, or we have confirmation that they will never be able to interpolate
|
||||
foreach (var secondary in secondarySet)
|
||||
{
|
||||
var secondaryQueue = this.secondaryQueues[secondary];
|
||||
|
||||
// clear the secondary queue as needed
|
||||
while (secondaryQueue.Queue.Count != 0 && secondaryQueue.Queue.Peek().OriginatingTime < this.lastResults[secondary].ObsoleteTime)
|
||||
{
|
||||
this.InSecondaries[secondary].Recycle(secondaryQueue.Queue.Dequeue());
|
||||
}
|
||||
}
|
||||
|
||||
Array.Clear(this.lastResults, 0, this.lastResults.Length);
|
||||
this.InPrimary.Recycle(primary);
|
||||
this.primaryQueue.Dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,11 +13,7 @@ namespace Microsoft.Psi.Components
|
|||
/// <typeparam name="T">The output type.</typeparam>
|
||||
/// <remarks>
|
||||
/// The static functions provided by the <see cref="Generators"/> wrap <see cref="Generator{T}"/>
|
||||
/// and are designed to make the common cases easier:
|
||||
/// <see cref="Generators.Sequence{T}(Pipeline, IEnumerable{ValueTuple{T, DateTime}})"/>
|
||||
/// <see cref="Generators.Sequence{T}(Pipeline, IEnumerable{T}, TimeSpan, DateTime?)"/>
|
||||
/// <see cref="Generators.Sequence{T}(Pipeline, IEnumerator{ValueTuple{T, DateTime}})"/>
|
||||
/// <see cref="Generators.Sequence{T}(Pipeline, IEnumerator{T}, TimeSpan, DateTime?)"/>.
|
||||
/// and are designed to make the common cases easier.
|
||||
/// </remarks>
|
||||
public class Generator<T> : Generator, IProducer<T>
|
||||
{
|
||||
|
@ -28,11 +24,13 @@ namespace Microsoft.Psi.Components
|
|||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline to attach to.</param>
|
||||
/// <param name="enumerator">A lazy enumerator of data.</param>
|
||||
/// <param name="interval">An optional timespan interval used to increment time on each generated message. Defaults to 1 tick.</param>
|
||||
/// <param name="interval">The interval used to increment time on each generated message.</param>
|
||||
/// <param name="alignDateTime">If non-null, this parameter specifies a time to align the generator messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with the specified time.</param>
|
||||
public Generator(Pipeline pipeline, IEnumerator<T> enumerator, TimeSpan interval = default(TimeSpan), DateTime? alignDateTime = null)
|
||||
: this(pipeline, CreateEnumerator(pipeline, enumerator, (interval == default(TimeSpan)) ? new TimeSpan(1) : interval, alignDateTime))
|
||||
/// <param name="isInfiniteSource">If true, mark this Generator instance as representing an infinite source (e.g., a live-running sensor).
|
||||
/// If false (default), it represents a finite source (e.g., Generating messages based on a finite file or IEnumerable).</param>
|
||||
public Generator(Pipeline pipeline, IEnumerator<T> enumerator, TimeSpan interval, DateTime? alignDateTime = null, bool isInfiniteSource = false)
|
||||
: this(pipeline, CreateEnumerator(pipeline, enumerator, interval, alignDateTime), isInfiniteSource)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -41,8 +39,10 @@ namespace Microsoft.Psi.Components
|
|||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline to attach to.</param>
|
||||
/// <param name="enumerator">A lazy enumerator of data.</param>
|
||||
public Generator(Pipeline pipeline, IEnumerator<ValueTuple<T, DateTime>> enumerator)
|
||||
: base(pipeline)
|
||||
/// <param name="isInfiniteSource">If true, mark this Generator instance as representing an infinite source (e.g., a live-running sensor).
|
||||
/// If false (default), it represents a finite source (e.g., Generating messages based on a finite file or IEnumerable).</param>
|
||||
public Generator(Pipeline pipeline, IEnumerator<ValueTuple<T, DateTime>> enumerator, bool isInfiniteSource = false)
|
||||
: base(pipeline, isInfiniteSource)
|
||||
{
|
||||
this.Out = pipeline.CreateEmitter<T>(this, nameof(this.Out));
|
||||
this.enumerator = enumerator;
|
||||
|
|
|
@ -5,176 +5,32 @@ namespace Microsoft.Psi.Components
|
|||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Performs a merge between a pair of streams.
|
||||
/// Component that joins multiple streams using a reproducible interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">The type the messages on the primary stream.</typeparam>
|
||||
/// <typeparam name="TSecondary">The type messages on the secondary stream.</typeparam>
|
||||
/// <typeparam name="TInterpolation">The type of the interpolation result on the secondary stream.</typeparam>
|
||||
/// <typeparam name="TOut">The type of output message.</typeparam>
|
||||
public class Join<TPrimary, TSecondary, TOut> : IProducer<TOut>
|
||||
public class Join<TPrimary, TSecondary, TInterpolation, TOut> : Fuse<TPrimary, TSecondary, TInterpolation, TOut>
|
||||
{
|
||||
private readonly Queue<Message<TPrimary>> primaryQueue = new Queue<Message<TPrimary>>(); // to be paired
|
||||
private readonly Match.Interpolator<TSecondary> interpolator;
|
||||
private readonly Func<TPrimary, TSecondary[], TOut> outputCreator;
|
||||
private readonly Func<TPrimary, IEnumerable<int>> secondarySelector;
|
||||
private (Queue<Message<TSecondary>> Queue, DateTime? ClosedOriginatingTime)[] secondaryQueues;
|
||||
private Receiver<TSecondary>[] inSecondaries;
|
||||
private IEnumerable<int> defaultSecondarySet;
|
||||
private Pipeline pipeline;
|
||||
|
||||
// temp buffers
|
||||
private TSecondary[] lastValues;
|
||||
private MatchResult<TSecondary>[] lastResults;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Join{TPrimary, TSecondary, TOut}"/> class.
|
||||
/// Initializes a new instance of the <see cref="Join{TPrimary, TSecondary, TInterpolation, TOut}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Pipeline to which this component belongs.</param>
|
||||
/// <param name="interpolator">Interpolator with which to join.</param>
|
||||
/// <param name="interpolator">Reproducible interpolator to use when joining the streams.</param>
|
||||
/// <param name="outputCreator">Mapping function from message pair to output.</param>
|
||||
/// <param name="secondaryCount">Number of secondary streams.</param>
|
||||
/// <param name="secondarySelector">Selector function mapping primary messages to secondary stream indices.</param>
|
||||
public Join(
|
||||
Pipeline pipeline,
|
||||
Match.Interpolator<TSecondary> interpolator,
|
||||
Func<TPrimary, TSecondary[], TOut> outputCreator,
|
||||
ReproducibleInterpolator<TSecondary, TInterpolation> interpolator,
|
||||
Func<TPrimary, TInterpolation[], TOut> outputCreator,
|
||||
int secondaryCount = 1,
|
||||
Func<TPrimary, IEnumerable<int>> secondarySelector = null)
|
||||
: base()
|
||||
: base(pipeline, interpolator, outputCreator, secondaryCount, secondarySelector)
|
||||
{
|
||||
this.pipeline = pipeline;
|
||||
this.Out = pipeline.CreateEmitter<TOut>(this, nameof(this.Out));
|
||||
this.InPrimary = pipeline.CreateReceiver<TPrimary>(this, this.ReceivePrimary, nameof(this.InPrimary));
|
||||
this.interpolator = interpolator;
|
||||
this.outputCreator = outputCreator;
|
||||
this.secondarySelector = secondarySelector;
|
||||
this.inSecondaries = new Receiver<TSecondary>[secondaryCount];
|
||||
this.secondaryQueues = new ValueTuple<Queue<Message<TSecondary>>, DateTime?>[secondaryCount];
|
||||
this.lastValues = new TSecondary[secondaryCount];
|
||||
this.lastResults = new MatchResult<TSecondary>[secondaryCount];
|
||||
this.defaultSecondarySet = Enumerable.Range(0, secondaryCount);
|
||||
for (int i = 0; i < secondaryCount; i++)
|
||||
{
|
||||
this.secondaryQueues[i] = (new Queue<Message<TSecondary>>(), null);
|
||||
var id = i; // needed to make the closure below byval
|
||||
var receiver = pipeline.CreateReceiver<TSecondary>(this, (d, e) => this.ReceiveSecondary(id, d, e), "InSecondary" + i);
|
||||
receiver.Unsubscribed += closedOriginatingTime => this.SecondaryClosed(id, closedOriginatingTime);
|
||||
this.inSecondaries[i] = receiver;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Emitter<TOut> Out { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets primary input receiver.
|
||||
/// </summary>
|
||||
public Receiver<TPrimary> InPrimary { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets collection of secondary receivers.
|
||||
/// </summary>
|
||||
public IList<Receiver<TSecondary>> InSecondaries => this.inSecondaries;
|
||||
|
||||
/// <summary>
|
||||
/// Add input receiver.
|
||||
/// </summary>
|
||||
/// <returns>Receiver.</returns>
|
||||
public Receiver<TSecondary> AddInput()
|
||||
{
|
||||
// use the sync context to protect the queues from concurrent access
|
||||
var syncContext = this.Out.SyncContext;
|
||||
syncContext.Lock();
|
||||
|
||||
try
|
||||
{
|
||||
var lastIndex = this.inSecondaries.Length;
|
||||
var count = lastIndex + 1;
|
||||
Array.Resize(ref this.inSecondaries, count);
|
||||
var newInput = this.inSecondaries[lastIndex] = this.pipeline.CreateReceiver<TSecondary>(this, (d, e) => this.ReceiveSecondary(lastIndex, d, e), "InSecondary" + lastIndex);
|
||||
newInput.Unsubscribed += closedOriginatingTime => this.SecondaryClosed(lastIndex, closedOriginatingTime);
|
||||
Array.Resize(ref this.secondaryQueues, count);
|
||||
this.secondaryQueues[lastIndex] = (new Queue<Message<TSecondary>>(), null);
|
||||
Array.Resize(ref this.lastResults, count);
|
||||
Array.Resize(ref this.lastValues, count);
|
||||
this.defaultSecondarySet = Enumerable.Range(0, count);
|
||||
return newInput;
|
||||
}
|
||||
finally
|
||||
{
|
||||
syncContext.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReceivePrimary(TPrimary message, Envelope e)
|
||||
{
|
||||
var clone = message.DeepClone(this.InPrimary.Recycler);
|
||||
this.primaryQueue.Enqueue(Message.Create(clone, e));
|
||||
this.Publish();
|
||||
}
|
||||
|
||||
private void ReceiveSecondary(int id, TSecondary message, Envelope e)
|
||||
{
|
||||
var clone = message.DeepClone(this.InSecondaries[id].Recycler);
|
||||
this.secondaryQueues[id].Queue.Enqueue(Message.Create(clone, e));
|
||||
this.Publish();
|
||||
}
|
||||
|
||||
private void SecondaryClosed(int index, DateTime closedOriginatingTime)
|
||||
{
|
||||
this.secondaryQueues[index].ClosedOriginatingTime = closedOriginatingTime;
|
||||
this.Publish();
|
||||
}
|
||||
|
||||
private void Publish()
|
||||
{
|
||||
while (this.primaryQueue.Count > 0)
|
||||
{
|
||||
var primary = this.primaryQueue.Peek();
|
||||
bool ready = true;
|
||||
var secondarySet = (this.secondarySelector != null) ? this.secondarySelector(primary.Data) : this.defaultSecondarySet;
|
||||
foreach (var secondaryIndex in secondarySet)
|
||||
{
|
||||
var secondaryQueue = this.secondaryQueues[secondaryIndex];
|
||||
var matchResult = this.interpolator.Match(primary.OriginatingTime, secondaryQueue.Queue, secondaryQueue.ClosedOriginatingTime);
|
||||
if (matchResult.Type == MatchResultType.InsufficientData)
|
||||
{
|
||||
// we need to wait more
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastResults[secondaryIndex] = matchResult;
|
||||
this.lastValues[secondaryIndex] = matchResult.Value;
|
||||
ready = ready && matchResult.Type == MatchResultType.Created;
|
||||
}
|
||||
|
||||
// if all secondaries match a value, publish the resulting set
|
||||
if (ready)
|
||||
{
|
||||
// publish
|
||||
var result = this.outputCreator(primary.Data, this.lastValues);
|
||||
this.Out.Post(result, primary.OriginatingTime);
|
||||
Array.Clear(this.lastValues, 0, this.lastValues.Length);
|
||||
}
|
||||
|
||||
// if we got here, all secondaries either successfully match a value, or we have confirmation that they will never be able to match it
|
||||
foreach (var secondaryIndex in secondarySet)
|
||||
{
|
||||
var secondaryQueue = this.secondaryQueues[secondaryIndex];
|
||||
|
||||
// clear the secondary queue as needed
|
||||
while (secondaryQueue.Queue.Count != 0 && secondaryQueue.Queue.Peek().OriginatingTime < this.lastResults[secondaryIndex].ObsoleteTime)
|
||||
{
|
||||
this.InSecondaries[secondaryIndex].Recycle(secondaryQueue.Queue.Dequeue());
|
||||
}
|
||||
}
|
||||
|
||||
Array.Clear(this.lastResults, 0, this.lastResults.Length);
|
||||
this.InPrimary.Recycle(primary);
|
||||
this.primaryQueue.Dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Component that joins multiple streams using a reproducible interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">The type the messages on the primary stream.</typeparam>
|
||||
/// <typeparam name="TSecondary">The type messages on the secondary stream.</typeparam>
|
||||
/// <typeparam name="TOut">The type of output message.</typeparam>
|
||||
public class Join<TPrimary, TSecondary, TOut> : Join<TPrimary, TSecondary, TSecondary, TOut>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Join{TPrimary, TSecondary, TOut}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Pipeline to which this component belongs.</param>
|
||||
/// <param name="interpolator">Reproducible interpolator to use when joining the streams.</param>
|
||||
/// <param name="outputCreator">Mapping function from message pair to output.</param>
|
||||
/// <param name="secondaryCount">Number of secondary streams.</param>
|
||||
/// <param name="secondarySelector">Selector function mapping primary messages to secondary stream indices.</param>
|
||||
public Join(
|
||||
Pipeline pipeline,
|
||||
ReproducibleInterpolator<TSecondary, TSecondary> interpolator,
|
||||
Func<TPrimary, TSecondary[], TOut> outputCreator,
|
||||
int secondaryCount = 1,
|
||||
Func<TPrimary, IEnumerable<int>> secondarySelector = null)
|
||||
: base(pipeline, interpolator, outputCreator, secondaryCount, secondarySelector)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,9 +14,8 @@ namespace Microsoft.Psi.Components
|
|||
public class Pair<TPrimary, TSecondary, TOut> : IProducer<TOut>
|
||||
{
|
||||
private readonly Func<TPrimary, TSecondary, TOut> outputCreator;
|
||||
private readonly Pipeline pipeline;
|
||||
private bool secondaryValueReady = false;
|
||||
private TSecondary lastSecondaryValue = default(TSecondary);
|
||||
private TSecondary lastSecondaryValue = default;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Pair{TPrimary, TSecondary, TOut}"/> class.
|
||||
|
@ -28,7 +27,6 @@ namespace Microsoft.Psi.Components
|
|||
Func<TPrimary, TSecondary, TOut> outputCreator)
|
||||
: base()
|
||||
{
|
||||
this.pipeline = pipeline;
|
||||
this.outputCreator = outputCreator;
|
||||
this.Out = pipeline.CreateEmitter<TOut>(this, nameof(this.Out));
|
||||
this.InPrimary = pipeline.CreateReceiver<TPrimary>(this, this.ReceivePrimary, nameof(this.InPrimary));
|
||||
|
|
|
@ -42,8 +42,9 @@ namespace Microsoft.Psi.Components
|
|||
/// <param name="pipeline">Pipeline to which this component belongs.</param>
|
||||
/// <param name="vectorSize">Vector size.</param>
|
||||
/// <param name="transform">Function mapping keyed input producers to output producers.</param>
|
||||
/// <param name="joinOrDefault">Whether to do an "...OrDefault" join.</param>
|
||||
public ParallelFixedLength(Pipeline pipeline, int vectorSize, Func<int, IProducer<TIn>, IProducer<TOut>> transform, bool joinOrDefault)
|
||||
/// <param name="outputDefaultIfDropped">When true, a result is produced even if a message is dropped in processing one of the input elements. In this case the corresponding output element is set to a default value.</param>
|
||||
/// <param name="defaultValue">Default value to use when messages are dropped in processing one of the input elements.</param>
|
||||
public ParallelFixedLength(Pipeline pipeline, int vectorSize, Func<int, IProducer<TIn>, IProducer<TOut>> transform, bool outputDefaultIfDropped, TOut defaultValue = default)
|
||||
{
|
||||
this.In = pipeline.CreateReceiver<TIn[]>(this, this.Receive, nameof(this.In));
|
||||
this.branches = new Emitter<TIn>[vectorSize];
|
||||
|
@ -59,8 +60,8 @@ namespace Microsoft.Psi.Components
|
|||
branchResults[i] = connectorOut;
|
||||
}
|
||||
|
||||
var interpolator = joinOrDefault ? Match.ExactOrDefault<TOut>() : Match.Exact<TOut>();
|
||||
this.join = Operators.Join(branchResults, interpolator, pipeline: pipeline);
|
||||
var interpolator = outputDefaultIfDropped ? Reproducible.ExactOrDefault<TOut>(defaultValue) : Reproducible.Exact<TOut>();
|
||||
this.join = Operators.Join(branchResults, interpolator);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Microsoft.Psi.Components
|
|||
private readonly Connector<Dictionary<TKey, TIn>> inConnector;
|
||||
private readonly Connector<Dictionary<TKey, TOut>> outConnector;
|
||||
private readonly Pipeline pipeline;
|
||||
private readonly Join<Dictionary<TKey, int>, TOut, Dictionary<TKey, TOut>> join;
|
||||
private readonly Join<Dictionary<TKey, int>, TOut, TOut, Dictionary<TKey, TOut>> join;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ParallelSparse{TIn, TKey, TOut}"/> class.
|
||||
|
@ -41,9 +41,10 @@ namespace Microsoft.Psi.Components
|
|||
/// </summary>
|
||||
/// <param name="pipeline">Pipeline to which this component belongs.</param>
|
||||
/// <param name="transform">Function mapping keyed input producers to output producers.</param>
|
||||
/// <param name="joinOrDefault">Whether to do an "...OrDefault" join.</param>
|
||||
/// <param name="outputDefaultIfDropped">When true, a result is produced even if a message is dropped in processing one of the input elements. In this case the corresponding output element is set to a default value.</param>
|
||||
/// <param name="defaultValue">Default value to use when messages are dropped in processing one of the input elements.</param>
|
||||
/// <param name="branchTerminationPolicy">Predicate function determining whether and when (originating time) to terminate branches (defaults to when key no longer present), given the current key, message payload (dictionary) and originating time.</param>
|
||||
public ParallelSparse(Pipeline pipeline, Func<TKey, IProducer<TIn>, IProducer<TOut>> transform, bool joinOrDefault, Func<TKey, Dictionary<TKey, TIn>, DateTime, (bool, DateTime)> branchTerminationPolicy = null)
|
||||
public ParallelSparse(Pipeline pipeline, Func<TKey, IProducer<TIn>, IProducer<TOut>> transform, bool outputDefaultIfDropped = false, TOut defaultValue = default, Func<TKey, Dictionary<TKey, TIn>, DateTime, (bool, DateTime)> branchTerminationPolicy = null)
|
||||
: base(pipeline)
|
||||
{
|
||||
this.pipeline = pipeline;
|
||||
|
@ -51,7 +52,7 @@ namespace Microsoft.Psi.Components
|
|||
|
||||
var splitter = new ParallelSplitter(this, transform, branchTerminationPolicy, o => o.PipeTo(this.join.AddInput(), true));
|
||||
this.inConnector.PipeTo(splitter);
|
||||
var interpolator = joinOrDefault ? Match.ExactOrDefault<TOut>() : Match.Exact<TOut>();
|
||||
var interpolator = outputDefaultIfDropped ? Reproducible.ExactOrDefault(defaultValue) : Reproducible.Exact<TOut>();
|
||||
this.join = Operators.Join(splitter.ActiveBranches, Enumerable.Empty<IProducer<TOut>>(), interpolator);
|
||||
this.outConnector = this.CreateOutputConnectorTo<Dictionary<TKey, TOut>>(pipeline, nameof(this.outConnector));
|
||||
this.join.PipeTo(this.outConnector);
|
||||
|
|
|
@ -39,17 +39,24 @@ namespace Microsoft.Psi.Components
|
|||
/// </summary>
|
||||
/// <param name="pipeline">Pipeline to which this component belongs.</param>
|
||||
/// <param name="transform">Function mapping keyed input producers to output producers.</param>
|
||||
/// <param name="joinOrDefault">Whether to do an "...OrDefault" join.</param>
|
||||
public ParallelVariableLength(Pipeline pipeline, Func<int, IProducer<TIn>, IProducer<TOut>> transform, bool joinOrDefault)
|
||||
/// <param name="outputDefaultIfDropped">When true, a result is produced even if a message is dropped in processing one of the input elements. In this case the corresponding output element is set to a default value.</param>
|
||||
/// <param name="defaultValue">Default value to use when messages are dropped in processing one of the input elements.</param>
|
||||
public ParallelVariableLength(Pipeline pipeline, Func<int, IProducer<TIn>, IProducer<TOut>> transform, bool outputDefaultIfDropped = false, TOut defaultValue = default)
|
||||
{
|
||||
this.pipeline = pipeline;
|
||||
this.parallelTransform = transform;
|
||||
this.In = pipeline.CreateReceiver<TIn[]>(this, this.Receive, nameof(this.In));
|
||||
this.activeBranchesEmitter = pipeline.CreateEmitter<int>(this, nameof(this.activeBranchesEmitter));
|
||||
var interpolator = joinOrDefault ?
|
||||
Match.BestOrDefault<TOut>(new RelativeTimeInterval(-default(TimeSpan), default(TimeSpan))) :
|
||||
Match.Exact<TOut>();
|
||||
this.join = Operators.Join(this.activeBranchesEmitter, Enumerable.Empty<IProducer<TOut>>(), interpolator);
|
||||
var interpolator = outputDefaultIfDropped ? Reproducible.ExactOrDefault<TOut>(defaultValue) : Reproducible.Exact<TOut>();
|
||||
|
||||
this.join = new Join<int, TOut, TOut[]>(
|
||||
pipeline,
|
||||
interpolator,
|
||||
(count, values) => values,
|
||||
0,
|
||||
count => Enumerable.Range(0, count));
|
||||
|
||||
this.activeBranchesEmitter.PipeTo(this.join.InPrimary);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Component that implements a stream sampler.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of messages to sample.</typeparam>
|
||||
public class Sampler<T> : ConsumerProducer<T, T>
|
||||
{
|
||||
private readonly Queue<Message<T>> inputQueue = new Queue<Message<T>>();
|
||||
private readonly Match.Interpolator<T> interpolator;
|
||||
private readonly TimeSpan samplingInterval;
|
||||
private DateTime nextPublishTime;
|
||||
private DateTime? closingOriginatingTime = null;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Sampler{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Pipeline to which this component belongs.</param>
|
||||
/// <param name="interpolator">Interpolator used to sample.</param>
|
||||
/// <param name="samplingInterval">Sampling interval.</param>
|
||||
public Sampler(Pipeline pipeline, Match.Interpolator<T> interpolator, TimeSpan samplingInterval)
|
||||
: base(pipeline)
|
||||
{
|
||||
this.In.Unsubscribed += closingOriginatingTime => this.closingOriginatingTime = closingOriginatingTime;
|
||||
this.interpolator = interpolator;
|
||||
this.samplingInterval = samplingInterval;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Receive(T message, Envelope e)
|
||||
{
|
||||
var clone = message.DeepClone(this.In.Recycler);
|
||||
this.inputQueue.Enqueue(Message.Create(clone, e));
|
||||
this.Publish(e.OriginatingTime);
|
||||
}
|
||||
|
||||
private void Publish(DateTime now)
|
||||
{
|
||||
if (this.nextPublishTime == default(DateTime))
|
||||
{
|
||||
this.nextPublishTime = now;
|
||||
}
|
||||
|
||||
while (this.nextPublishTime <= now)
|
||||
{
|
||||
var matchResult = this.interpolator.Match(this.nextPublishTime, this.inputQueue, this.closingOriginatingTime);
|
||||
if (matchResult.Type == MatchResultType.InsufficientData)
|
||||
{
|
||||
// we need to wait more
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchResult.Type == MatchResultType.Created)
|
||||
{
|
||||
// publish
|
||||
this.Out.Post(matchResult.Value, this.nextPublishTime);
|
||||
}
|
||||
|
||||
// clear the queue as needed
|
||||
while (this.inputQueue.Peek().OriginatingTime < matchResult.ObsoleteTime)
|
||||
{
|
||||
this.In.Recycle(this.inputQueue.Dequeue());
|
||||
}
|
||||
|
||||
this.nextPublishTime += this.samplingInterval;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -253,7 +253,6 @@ namespace Microsoft.Psi.Diagnostics
|
|||
var input = this.graphs[pipeline.Id].PipelineElements[element.Id].Receivers[receiver.Id];
|
||||
input.DroppedCount++;
|
||||
input.QueueSize = queueSize;
|
||||
input.AddMessageLatencyAtEmitter(envelope);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Diagnostics
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Class that represents diagnostics collector configuration information.
|
||||
/// </summary>
|
||||
public class DiagnosticsConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Default configuration.
|
||||
/// </summary>
|
||||
public static readonly DiagnosticsConfiguration Default = new DiagnosticsConfiguration();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DiagnosticsConfiguration"/> class.
|
||||
/// </summary>
|
||||
public DiagnosticsConfiguration()
|
||||
{
|
||||
this.SamplingInterval = TimeSpan.FromMilliseconds(100);
|
||||
this.TrackMessageSize = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets sampling interval.
|
||||
/// </summary>
|
||||
public TimeSpan SamplingInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to track message sizes (notable performance penalty).
|
||||
/// </summary>
|
||||
public bool TrackMessageSize { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi.Diagnostics
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Represents diagnostic information about a pipeline.
|
||||
/// </summary>
|
||||
public static class DiagnosticsQueryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all pipeline diagnostics (including descendant subpipelines).
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Root pipeline diagnostics.</param>
|
||||
/// <returns>All pipeline diagnostics.</returns>
|
||||
public static IEnumerable<PipelineDiagnostics> GetAllPipelineDiagnostics(this PipelineDiagnostics pipeline)
|
||||
{
|
||||
yield return pipeline;
|
||||
foreach (var child in pipeline.Subpipelines.Values)
|
||||
{
|
||||
foreach (var descendant in child.GetAllPipelineDiagnostics())
|
||||
{
|
||||
yield return descendant;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all pipeline element diagnostics within a collection of pipeline diagnostics.
|
||||
/// </summary>
|
||||
/// <param name="pipelines">Collection of pipeline diagnostics.</param>
|
||||
/// <returns>All pipeline element diagnostics within.</returns>
|
||||
public static IEnumerable<PipelineDiagnostics.PipelineElementDiagnostics> GetAllPipelineElements(this IEnumerable<PipelineDiagnostics> pipelines)
|
||||
{
|
||||
foreach (var p in pipelines)
|
||||
{
|
||||
foreach (var pe in p.PipelineElements.Values)
|
||||
{
|
||||
yield return pe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all pipeline element diagnostics within a pipeline diagnostics (and all descendant subpipelines).
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Root pipeline diagnostics.</param>
|
||||
/// <returns>Collection of all pipeline element diagnostics within.</returns>
|
||||
public static IEnumerable<PipelineDiagnostics.PipelineElementDiagnostics> GetAllPipelineElementDiagnostics(this PipelineDiagnostics pipeline)
|
||||
{
|
||||
return pipeline.GetAllPipelineDiagnostics().GetAllPipelineElements();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all emitter diagnostics within a collection of pipeline element diagnostics.
|
||||
/// </summary>
|
||||
/// <param name="pipelineElements">Collection of pipeline element diagnostics.</param>
|
||||
/// <returns>Collection of all emitter diagnostics within.</returns>
|
||||
public static IEnumerable<PipelineDiagnostics.EmitterDiagnostics> GetAllEmitterDiagnostics(this IEnumerable<PipelineDiagnostics.PipelineElementDiagnostics> pipelineElements)
|
||||
{
|
||||
foreach (var pe in pipelineElements)
|
||||
{
|
||||
foreach (var e in pe.Emitters.Values)
|
||||
{
|
||||
yield return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all emitter diagnostics within a collection of pipeline diagnostics.
|
||||
/// </summary>
|
||||
/// <param name="pipelines">Collection of pipeline diagnostics.</param>
|
||||
/// <returns>Collection of all emitter diagnostics within.</returns>
|
||||
public static IEnumerable<PipelineDiagnostics.EmitterDiagnostics> GetAllEmitterDiagnostics(this IEnumerable<PipelineDiagnostics> pipelines)
|
||||
{
|
||||
return pipelines.GetAllPipelineElements().GetAllEmitterDiagnostics();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all emitter diagnostics within a pipeline diagnostics (and all descendant subpipelines).
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Root pipeline diagnostics.</param>
|
||||
/// <returns>Collection of all emitter diagnostics within.</returns>
|
||||
public static IEnumerable<PipelineDiagnostics.EmitterDiagnostics> GetAllEmitterDiagnostics(this PipelineDiagnostics pipeline)
|
||||
{
|
||||
return pipeline.GetAllPipelineDiagnostics().GetAllEmitterDiagnostics();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collection of all receiver diagnostics within a collection of pipeline element diagnostics.
|
||||
/// </summary>
|
||||
/// <param name="pipelineElements">Collection of pipeline element diagnostics.</param>
|
||||
/// <returns>Collection of all receiver diagnostics within.</returns>
|
||||
public static IEnumerable<PipelineDiagnostics.ReceiverDiagnostics> GetAllReceiverDiagnostics(this IEnumerable<PipelineDiagnostics.PipelineElementDiagnostics> pipelineElements)
|
||||
{
|
||||
foreach (var pe in pipelineElements)
|
||||
{
|
||||
foreach (var r in pe.Receivers.Values)
|
||||
{
|
||||
yield return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collection of all receiver diagnostics within a collection of pipeline diagnostics.
|
||||
/// </summary>
|
||||
/// <param name="pipelines">Collection of pipeline diagnostics.</param>
|
||||
/// <returns>Collection of all receiver diagnostics within.</returns>
|
||||
public static IEnumerable<PipelineDiagnostics.ReceiverDiagnostics> GetAllReceiverDiagnostics(this IEnumerable<PipelineDiagnostics> pipelines)
|
||||
{
|
||||
return pipelines.GetAllPipelineElements().GetAllReceiverDiagnostics();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all receiver diagnostics within a pipeline diagnostics (and all descendant subpipelines).
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Root pipeline diagnostics.</param>
|
||||
/// <returns>Collection of all receiver diagnostics within.</returns>
|
||||
public static IEnumerable<PipelineDiagnostics.ReceiverDiagnostics> GetAllReceiverDiagnostics(this PipelineDiagnostics pipeline)
|
||||
{
|
||||
return pipeline.GetAllPipelineDiagnostics().GetAllReceiverDiagnostics();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets count of pipelines.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Root pipeline diagnostics.</param>
|
||||
/// <param name="predicate">Predicate expression filtering pipeline diagnostics.</param>
|
||||
/// <returns>Pipeline count.</returns>
|
||||
public static int GetPipelineCount(this PipelineDiagnostics pipeline, Func<PipelineDiagnostics, bool> predicate = null)
|
||||
{
|
||||
return pipeline.GetAllPipelineDiagnostics().Where(p => predicate == null ? true : predicate(p)).Count();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets count of pipeline elements.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Root pipeline diagnostics.</param>
|
||||
/// <param name="predicate">Predicate expression filtering pipeline element diagnostics.</param>
|
||||
/// <returns>Pipeline element count.</returns>
|
||||
public static int GetPipelineElementCount(this PipelineDiagnostics pipeline, Func<PipelineDiagnostics.PipelineElementDiagnostics, bool> predicate = null)
|
||||
{
|
||||
return pipeline.GetAllPipelineElementDiagnostics().Where(e => predicate == null ? true : predicate(e)).Count();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets emitter count within pipeline and descendant.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Root pipeline diagnostics.</param>
|
||||
/// <param name="predicate">Predicate expression filtering emitter diagnostics.</param>
|
||||
/// <returns>Emitter count.</returns>
|
||||
public static int GetEmitterCount(this PipelineDiagnostics pipeline, Func<PipelineDiagnostics.EmitterDiagnostics, bool> predicate = null)
|
||||
{
|
||||
return pipeline.GetAllEmitterDiagnostics().Where(e => predicate == null ? true : predicate(e)).Count();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets receiver count within pipeline and descendant.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Root pipeline diagnostics.</param>
|
||||
/// <param name="predicate">Predicate expression filtering receiver diagnostics.</param>
|
||||
/// <returns>Receiver count.</returns>
|
||||
public static int GetReceiverCount(this PipelineDiagnostics pipeline, Func<PipelineDiagnostics.ReceiverDiagnostics, bool> predicate = null)
|
||||
{
|
||||
return pipeline.GetAllReceiverDiagnostics().Where(r => predicate == null ? true : predicate(r)).Count();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets throttled receiver count within pipeline and descendant.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Root pipeline diagnostics.</param>
|
||||
/// <param name="predicate">Predicate expression filtering receiver diagnostics.</param>
|
||||
/// <returns>Queued message count.</returns>
|
||||
public static int GetQueuedMessageCount(this PipelineDiagnostics pipeline, Func<PipelineDiagnostics.ReceiverDiagnostics, bool> predicate = null)
|
||||
{
|
||||
return pipeline.GetAllReceiverDiagnostics().Where(r => predicate == null ? true : predicate(r)).Select(r => r.QueueSize).Sum();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets dropped message count across receivers within pipeline and descendant.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Root pipeline diagnostics.</param>
|
||||
/// <param name="predicate">Predicate expression filtering receiver diagnostics.</param>
|
||||
/// <returns>Dropped message count.</returns>
|
||||
public static int GetDroppedMessageCount(this PipelineDiagnostics pipeline, Func<PipelineDiagnostics.ReceiverDiagnostics, bool> predicate = null)
|
||||
{
|
||||
return pipeline.GetAllReceiverDiagnostics().Where(r => predicate == null ? true : predicate(r)).Select(r => r.DroppedCount).Sum();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets processed message count across receivers within pipeline and descendant.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Root pipeline diagnostics.</param>
|
||||
/// <param name="predicate">Predicate expression filtering receiver diagnostics.</param>
|
||||
/// <returns>Processed message count.</returns>
|
||||
public static int GetProcessedMessageCount(this PipelineDiagnostics pipeline, Func<PipelineDiagnostics.ReceiverDiagnostics, bool> predicate = null)
|
||||
{
|
||||
return pipeline.GetAllReceiverDiagnostics().Where(r => predicate == null ? true : predicate(r)).Select(r => r.ProcessedCount).Sum();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets throttled receiver count across receivers within pipeline and descendant.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Root pipeline diagnostics.</param>
|
||||
/// <param name="predicate">Predicate expression filtering receiver diagnostics.</param>
|
||||
/// <returns>Throttled receiver count.</returns>
|
||||
public static int GetThrottledReceiverCount(this PipelineDiagnostics pipeline, Func<PipelineDiagnostics.ReceiverDiagnostics, bool> predicate = null)
|
||||
{
|
||||
return pipeline.GetAllReceiverDiagnostics().Where(r => r.Throttled && (predicate == null ? true : predicate(r))).Count();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute average time from a sequence of time spans (e.g. ProcessingTimeHistory).
|
||||
/// </summary>
|
||||
/// <param name="times">Sequence of time spans.</param>
|
||||
/// <returns>Average time (zero if empty).</returns>
|
||||
public static TimeSpan AverageTime(this IEnumerable<TimeSpan> times)
|
||||
{
|
||||
return TimeSpan.FromTicks(times.Count() > 0 ? (long)times.Select(t => t.Ticks).Average() : 0L);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute average size from a sequence of sizes (e.g. QueueSize).
|
||||
/// </summary>
|
||||
/// <param name="sizes">Sequence of sizes.</param>
|
||||
/// <returns>Average size (zero if empty).</returns>
|
||||
public static double AverageSize(this IEnumerable<int> sizes)
|
||||
{
|
||||
return sizes.Count() > 0 ? sizes.Average() : 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,12 +22,12 @@ namespace Microsoft.Psi.Diagnostics
|
|||
/// </summary>
|
||||
/// <param name="pipeline">Pipeline to which this component belongs.</param>
|
||||
/// <param name="collector">Diagnostics collector.</param>
|
||||
/// <param name="interval">Optional time interval at which to report diagnostics (default 100ms).</param>
|
||||
public DiagnosticsSampler(Pipeline pipeline, DiagnosticsCollector collector, TimeSpan interval = default(TimeSpan))
|
||||
/// <param name="config">Diagnostics configuration.</param>
|
||||
public DiagnosticsSampler(Pipeline pipeline, DiagnosticsCollector collector, DiagnosticsConfiguration config)
|
||||
{
|
||||
this.pipeline = pipeline;
|
||||
this.collector = collector;
|
||||
this.Interval = interval == default(TimeSpan) ? TimeSpan.FromMilliseconds(100) : interval;
|
||||
this.Config = config;
|
||||
this.Diagnostics = pipeline.CreateEmitter<PipelineDiagnostics>(this, nameof(this.Diagnostics));
|
||||
}
|
||||
|
||||
|
@ -49,9 +49,9 @@ namespace Microsoft.Psi.Diagnostics
|
|||
public Emitter<PipelineDiagnostics> Diagnostics { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets time interval at which diagnostics are reported.
|
||||
/// Gets the diagnostics configuration.
|
||||
/// </summary>
|
||||
public TimeSpan Interval { get; }
|
||||
public DiagnosticsConfiguration Config { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
|
@ -67,7 +67,7 @@ namespace Microsoft.Psi.Diagnostics
|
|||
if (this.collector != null)
|
||||
{
|
||||
this.timerDelegate = new Time.TimerDelegate((i, m, c, d1, d2) => this.Update());
|
||||
this.timer = Platform.Specific.TimerStart((uint)this.Interval.TotalMilliseconds, this.timerDelegate);
|
||||
this.timer = Platform.Specific.TimerStart((uint)this.Config.SamplingInterval.TotalMilliseconds, this.timerDelegate);
|
||||
this.running = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,9 +87,9 @@ namespace Microsoft.Psi
|
|||
/// <param name="threadCount">Number of threads.</param>
|
||||
/// <param name="allowSchedulingOnExternalThreads">Whether to allow scheduling on external threads.</param>
|
||||
/// <param name="enableDiagnostics">Whether to enable collecting and publishing diagnostics information on the Pipeline.Diagnostics stream.</param>
|
||||
/// <param name="diagnosticsInterval">Time interval at which to report diagnostics.</param>
|
||||
public Pipeline(string name, DeliveryPolicy deliveryPolicy, int threadCount, bool allowSchedulingOnExternalThreads, bool enableDiagnostics = false, TimeSpan diagnosticsInterval = default(TimeSpan))
|
||||
: this(name, deliveryPolicy, enableDiagnostics ? new DiagnosticsCollector() : null, diagnosticsInterval)
|
||||
/// <param name="diagnosticsConfig">Optional diagnostics configuration information.</param>
|
||||
public Pipeline(string name, DeliveryPolicy deliveryPolicy, int threadCount, bool allowSchedulingOnExternalThreads, bool enableDiagnostics = false, DiagnosticsConfiguration diagnosticsConfig = null)
|
||||
: this(name, deliveryPolicy, enableDiagnostics ? new DiagnosticsCollector() : null, diagnosticsConfig)
|
||||
{
|
||||
this.scheduler = new Scheduler(this.ErrorHandler, threadCount, allowSchedulingOnExternalThreads, name);
|
||||
this.schedulerContext = new SchedulerContext();
|
||||
|
@ -106,9 +106,9 @@ namespace Microsoft.Psi
|
|||
/// <param name="scheduler">Scheduler to be used.</param>
|
||||
/// <param name="schedulerContext">The scheduler context.</param>
|
||||
/// <param name="diagnosticsCollector">Collector with which to gather diagnostic information.</param>
|
||||
/// <param name="diagnosticsInterval">Time interval at which to report diagnostics information.</param>
|
||||
internal Pipeline(string name, DeliveryPolicy deliveryPolicy, Scheduler scheduler, SchedulerContext schedulerContext, DiagnosticsCollector diagnosticsCollector, TimeSpan diagnosticsInterval)
|
||||
: this(name, deliveryPolicy, diagnosticsCollector, diagnosticsInterval)
|
||||
/// <param name="diagnosticsConfig">Optional diagnostics configuration information.</param>
|
||||
internal Pipeline(string name, DeliveryPolicy deliveryPolicy, Scheduler scheduler, SchedulerContext schedulerContext, DiagnosticsCollector diagnosticsCollector, DiagnosticsConfiguration diagnosticsConfig)
|
||||
: this(name, deliveryPolicy, diagnosticsCollector, diagnosticsConfig)
|
||||
{
|
||||
this.scheduler = scheduler;
|
||||
this.schedulerContext = schedulerContext;
|
||||
|
@ -120,8 +120,8 @@ namespace Microsoft.Psi
|
|||
/// <param name="name">Pipeline name.</param>
|
||||
/// <param name="deliveryPolicy">Pipeline-level delivery policy.</param>
|
||||
/// <param name="diagnosticsCollector">Collector with which to gather diagnostic information.</param>
|
||||
/// <param name="diagnosticsInterval">Time interval at which to report diagnostics.</param>
|
||||
private Pipeline(string name, DeliveryPolicy deliveryPolicy, DiagnosticsCollector diagnosticsCollector, TimeSpan diagnosticsInterval)
|
||||
/// <param name="diagnosticsConfig">Optional diagnostics configuration information.</param>
|
||||
private Pipeline(string name, DeliveryPolicy deliveryPolicy, DiagnosticsCollector diagnosticsCollector, DiagnosticsConfiguration diagnosticsConfig)
|
||||
{
|
||||
this.id = Interlocked.Increment(ref lastPipelineId);
|
||||
this.name = name ?? "default";
|
||||
|
@ -131,12 +131,12 @@ namespace Microsoft.Psi
|
|||
this.FinalOriginatingTime = DateTime.MinValue;
|
||||
this.state = State.Initial;
|
||||
this.activationContext = new SchedulerContext();
|
||||
this.DiagnosticsInterval = diagnosticsInterval;
|
||||
this.DiagnosticsCollector = diagnosticsCollector;
|
||||
this.DiagnosticsConfiguration = diagnosticsConfig ?? DiagnosticsConfiguration.Default;
|
||||
this.DiagnosticsCollector?.PipelineCreate(this);
|
||||
if (!(this is Subpipeline))
|
||||
{
|
||||
this.Diagnostics = new DiagnosticsSampler(this, this.DiagnosticsCollector, diagnosticsInterval).Diagnostics;
|
||||
this.Diagnostics = new DiagnosticsSampler(this, this.DiagnosticsCollector, this.DiagnosticsConfiguration).Diagnostics;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -242,7 +242,7 @@ namespace Microsoft.Psi
|
|||
|
||||
internal DiagnosticsCollector DiagnosticsCollector { get; set; }
|
||||
|
||||
internal TimeSpan DiagnosticsInterval { get; set; }
|
||||
internal DiagnosticsConfiguration DiagnosticsConfiguration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the completion time of the latest completed source component.
|
||||
|
@ -271,11 +271,11 @@ namespace Microsoft.Psi
|
|||
/// <param name="threadCount">Number of threads.</param>
|
||||
/// <param name="allowSchedulingOnExternalThreads">Whether to allow scheduling on external threads.</param>
|
||||
/// <param name="enableDiagnostics">Whether to enable collecting and publishing diagnostics information on the Pipeline.Diagnostics stream.</param>
|
||||
/// <param name="diagnosticsInterval">Time interval at which to report diagnostics.</param>
|
||||
/// <param name="diagnosticsConfig">Optional diagnostics configuration information.</param>
|
||||
/// <returns>Created pipeline.</returns>
|
||||
public static Pipeline Create(string name = null, DeliveryPolicy deliveryPolicy = null, int threadCount = 0, bool allowSchedulingOnExternalThreads = false, bool enableDiagnostics = false, TimeSpan diagnosticsInterval = default(TimeSpan))
|
||||
public static Pipeline Create(string name = null, DeliveryPolicy deliveryPolicy = null, int threadCount = 0, bool allowSchedulingOnExternalThreads = false, bool enableDiagnostics = false, DiagnosticsConfiguration diagnosticsConfig = null)
|
||||
{
|
||||
return new Pipeline(name, deliveryPolicy, threadCount, allowSchedulingOnExternalThreads, enableDiagnostics, diagnosticsInterval);
|
||||
return new Pipeline(name, deliveryPolicy, threadCount, allowSchedulingOnExternalThreads, enableDiagnostics, diagnosticsConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -283,22 +283,22 @@ namespace Microsoft.Psi
|
|||
/// </summary>
|
||||
/// <param name="name">Pipeline name.</param>
|
||||
/// <param name="enableDiagnostics">Whether to enable collecting and publishing diagnostics information on the Pipeline.Diagnostics stream.</param>
|
||||
/// <param name="diagnosticsInterval">Time interval at which to report diagnostics.</param>
|
||||
/// <param name="diagnosticsConfig">Optional diagnostics configuration information.</param>
|
||||
/// <returns>Created pipeline.</returns>
|
||||
public static Pipeline Create(string name, bool enableDiagnostics, TimeSpan diagnosticsInterval = default(TimeSpan))
|
||||
public static Pipeline Create(string name, bool enableDiagnostics, DiagnosticsConfiguration diagnosticsConfig = null)
|
||||
{
|
||||
return Create(name, null, 0, false, enableDiagnostics, diagnosticsInterval);
|
||||
return Create(name, null, 0, false, enableDiagnostics, diagnosticsConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create pipeline.
|
||||
/// </summary>
|
||||
/// <param name="enableDiagnostics">Whether to enable collecting and publishing diagnostics information on the Pipeline.Diagnostics stream.</param>
|
||||
/// <param name="diagnosticsInterval">Time interval at which to report diagnostics.</param>
|
||||
/// <param name="diagnosticsConfig">Optional diagnostics configuration information.</param>
|
||||
/// <returns>Created pipeline.</returns>
|
||||
public static Pipeline Create(bool enableDiagnostics, TimeSpan diagnosticsInterval = default(TimeSpan))
|
||||
public static Pipeline Create(bool enableDiagnostics, DiagnosticsConfiguration diagnosticsConfig = null)
|
||||
{
|
||||
return Create(null, enableDiagnostics, diagnosticsInterval);
|
||||
return Create(null, enableDiagnostics, diagnosticsConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace Microsoft.Psi
|
|||
/// <param name="name">Subpipeline name (inherits "Sub<Parent>" name if unspecified)</Parent>.</param>
|
||||
/// <param name="deliveryPolicy">Pipeline-level delivery policy (inherits from parent if unspecified).</param>
|
||||
public Subpipeline(Pipeline parent, string name = null, DeliveryPolicy deliveryPolicy = null)
|
||||
: base(name ?? $"Sub{parent.Name}", deliveryPolicy ?? parent.DeliveryPolicy, parent.Scheduler, new SchedulerContext(), parent.DiagnosticsCollector, parent.DiagnosticsInterval)
|
||||
: base(name ?? $"Sub{parent.Name}", deliveryPolicy ?? parent.DeliveryPolicy, parent.Scheduler, new SchedulerContext(), parent.DiagnosticsCollector, parent.DiagnosticsConfiguration)
|
||||
{
|
||||
this.parent = parent;
|
||||
|
||||
|
|
|
@ -78,11 +78,6 @@
|
|||
<PackageReference Update="NETStandard.Library" Version="2.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Connectors\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
<Exec Command="$(MSBuildFrameworkToolsPath)ilasm @(ILFiles) /dll /output=$(OutDir)Microsoft.Psi.IL.dll /DEBUG=OPT" />
|
||||
</Target>
|
||||
|
|
|
@ -0,0 +1,470 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Psi.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods that simplify operator usage.
|
||||
/// </summary>
|
||||
public static partial class Operators
|
||||
{
|
||||
#region Scalar fuse operators
|
||||
|
||||
/// <summary>
|
||||
/// Fuse with values from a secondary stream based on a specified interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondary">Type of secondary messages.</typeparam>
|
||||
/// <typeparam name="TInterpolation">Type of the interpolation result.</typeparam>
|
||||
/// <typeparam name="TOut">Type of output messages.</typeparam>
|
||||
/// <param name="primary">Primary stream.</param>
|
||||
/// <param name="secondary">Secondary stream.</param>
|
||||
/// <param name="interpolator">Interpolator to use when fusing the streams.</param>
|
||||
/// <param name="outputCreator">Function mapping the primary and secondary messages to an output message type.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <returns>Stream of fused values.</returns>
|
||||
public static IProducer<TOut> Fuse<TPrimary, TSecondary, TInterpolation, TOut>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
Interpolator<TSecondary, TInterpolation> interpolator,
|
||||
Func<TPrimary, TInterpolation, TOut> outputCreator,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Fuse(
|
||||
primary,
|
||||
new[] { secondary },
|
||||
interpolator,
|
||||
(m, secondaryArray) => outputCreator(m, secondaryArray[0]),
|
||||
primaryDeliveryPolicy,
|
||||
secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fuse with values from a secondary stream based on a specified interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondary">Type of secondary messages.</typeparam>
|
||||
/// <typeparam name="TInterpolation">Type of the interpolation result.</typeparam>
|
||||
/// <param name="primary">Primary stream.</param>
|
||||
/// <param name="secondary">Secondary stream.</param>
|
||||
/// <param name="interpolator">Interpolator to use when fusing the streams.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <returns>Stream of fused values.</returns>
|
||||
public static IProducer<(TPrimary, TInterpolation)> Fuse<TPrimary, TSecondary, TInterpolation>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
Interpolator<TSecondary, TInterpolation> interpolator,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Fuse(primary, secondary, interpolator, ValueTuple.Create, primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
#endregion Scalar fuse operators
|
||||
|
||||
#region Tuple-flattening scalar fuse operators
|
||||
|
||||
/// <summary>
|
||||
/// Fuse with values from a secondary stream based on a specified interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimaryItem1">Type of item 1 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem2">Type of item 2 of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondary">Type of secondary messages.</typeparam>
|
||||
/// <typeparam name="TInterpolation">Type of the interpolation result.</typeparam>
|
||||
/// <param name="primary">Primary stream of tuples (arity 2).</param>
|
||||
/// <param name="secondary">Secondary stream.</param>
|
||||
/// <param name="interpolator">Interpolator to use when fusing the streams.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <returns>Stream of fused tuple values flattened to arity 3.</returns>
|
||||
public static IProducer<(TPrimaryItem1, TPrimaryItem2, TInterpolation)> Fuse<TPrimaryItem1, TPrimaryItem2, TSecondary, TInterpolation>(
|
||||
this IProducer<(TPrimaryItem1, TPrimaryItem2)> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
Interpolator<TSecondary, TInterpolation> interpolator,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Fuse(
|
||||
primary,
|
||||
secondary,
|
||||
interpolator,
|
||||
(p, s) => (p.Item1, p.Item2, s),
|
||||
primaryDeliveryPolicy,
|
||||
secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fuse with values from a secondary stream based on a specified interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimaryItem1">Type of item 1 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem2">Type of item 2 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem3">Type of item 3 of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondary">Type of secondary messages.</typeparam>
|
||||
/// <typeparam name="TInterpolation">Type of the interpolation result.</typeparam>
|
||||
/// <param name="primary">Primary stream of tuples (arity 3).</param>
|
||||
/// <param name="secondary">Secondary stream.</param>
|
||||
/// <param name="interpolator">Interpolator to use when fusing the streams.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <returns>Stream of fused tuple values flattened to arity 4.</returns>
|
||||
public static IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TInterpolation)> Fuse<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TSecondary, TInterpolation>(
|
||||
this IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3)> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
Interpolator<TSecondary, TInterpolation> interpolator,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Fuse(
|
||||
primary,
|
||||
secondary,
|
||||
interpolator,
|
||||
(p, s) => (p.Item1, p.Item2, p.Item3, s),
|
||||
primaryDeliveryPolicy,
|
||||
secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fuse with values from a secondary stream based on a specified interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimaryItem1">Type of item 1 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem2">Type of item 2 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem3">Type of item 3 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem4">Type of item 4 of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondary">Type of secondary messages.</typeparam>
|
||||
/// <typeparam name="TInterpolation">Type of the interpolation result.</typeparam>
|
||||
/// <param name="primary">Primary stream of tuples (arity 4).</param>
|
||||
/// <param name="secondary">Secondary stream.</param>
|
||||
/// <param name="interpolator">Interpolator to use when fusing the streams.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <returns>Stream of fused tuple values flattened to arity 5.</returns>
|
||||
public static IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TInterpolation)> Fuse<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TSecondary, TInterpolation>(
|
||||
this IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4)> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
Interpolator<TSecondary, TInterpolation> interpolator,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Fuse(
|
||||
primary,
|
||||
secondary,
|
||||
interpolator,
|
||||
(p, s) => (p.Item1, p.Item2, p.Item3, p.Item4, s),
|
||||
primaryDeliveryPolicy,
|
||||
secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fuse with values from a secondary stream based on a specified interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimaryItem1">Type of item 1 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem2">Type of item 2 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem3">Type of item 3 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem4">Type of item 4 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem5">Type of item 5 of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondary">Type of secondary messages.</typeparam>
|
||||
/// <typeparam name="TInterpolation">Type of the interpolation result.</typeparam>
|
||||
/// <param name="primary">Primary stream of tuples (arity 5).</param>
|
||||
/// <param name="secondary">Secondary stream.</param>
|
||||
/// <param name="interpolator">Interpolator to use when fusing the streams.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <returns>Stream of fused tuple values flattened to arity 6.</returns>
|
||||
public static IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TInterpolation)> Fuse<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TSecondary, TInterpolation>(
|
||||
this IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5)> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
Interpolator<TSecondary, TInterpolation> interpolator,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Fuse(
|
||||
primary,
|
||||
secondary,
|
||||
interpolator,
|
||||
(p, s) => (p.Item1, p.Item2, p.Item3, p.Item4, p.Item5, s),
|
||||
primaryDeliveryPolicy,
|
||||
secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fuse with values from a secondary stream based on a specified interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimaryItem1">Type of item 1 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem2">Type of item 2 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem3">Type of item 3 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem4">Type of item 4 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem5">Type of item 5 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem6">Type of item 6 of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondary">Type of secondary messages.</typeparam>
|
||||
/// <typeparam name="TInterpolation">Type of the interpolation result.</typeparam>
|
||||
/// <param name="primary">Primary stream of tuples (arity 6).</param>
|
||||
/// <param name="secondary">Secondary stream.</param>
|
||||
/// <param name="interpolator">Interpolator to use when fusing the streams.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <returns>Stream of fused tuple values flattened to arity 7.</returns>
|
||||
public static IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TPrimaryItem6, TInterpolation)> Fuse<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TPrimaryItem6, TSecondary, TInterpolation>(
|
||||
this IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TPrimaryItem6)> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
Interpolator<TSecondary, TInterpolation> interpolator,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Fuse(
|
||||
primary,
|
||||
secondary,
|
||||
interpolator,
|
||||
(p, s) => (p.Item1, p.Item2, p.Item3, p.Item4, p.Item5, p.Item6, s),
|
||||
primaryDeliveryPolicy,
|
||||
secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
#endregion Tuple-flattening scalar fuse operators
|
||||
|
||||
#region Reverse tuple-flattening scalar fuse operators
|
||||
|
||||
/// <summary>
|
||||
/// Fuse with values from a secondary stream based on a specified interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem1">Type of item 1 of secondary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem2">Type of item 2 of secondary messages.</typeparam>
|
||||
/// <param name="primary">Primary stream.</param>
|
||||
/// <param name="secondary">Secondary stream of tuples (arity 2).</param>
|
||||
/// <param name="interpolator">Interpolator to use when fusing the streams.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <returns>Stream of fused tuple values flattened to arity 3.</returns>
|
||||
public static IProducer<(TPrimary, TSecondaryItem1, TSecondaryItem2)> Fuse<TPrimary, TSecondaryItem1, TSecondaryItem2>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<(TSecondaryItem1, TSecondaryItem2)> secondary,
|
||||
Interpolator<(TSecondaryItem1, TSecondaryItem2), (TSecondaryItem1, TSecondaryItem2)> interpolator,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Fuse<TPrimary, (TSecondaryItem1, TSecondaryItem2), (TSecondaryItem1, TSecondaryItem2), (TPrimary, TSecondaryItem1, TSecondaryItem2)>(
|
||||
primary,
|
||||
secondary,
|
||||
interpolator,
|
||||
(p, s) => (p, s.Item1, s.Item2),
|
||||
primaryDeliveryPolicy,
|
||||
secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fuse with values from a secondary stream based on a specified interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem1">Type of item 1 of secondary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem2">Type of item 2 of secondary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem3">Type of item 3 of secondary messages.</typeparam>
|
||||
/// <param name="primary">Primary stream.</param>
|
||||
/// <param name="secondary">Secondary stream of tuples (arity 3).</param>
|
||||
/// <param name="interpolator">Interpolator to use when fusing the streams.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <returns>Stream of fused tuple values flattened to arity 4.</returns>
|
||||
public static IProducer<(TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3)> Fuse<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3)> secondary,
|
||||
Interpolator<(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3), (TSecondaryItem1, TSecondaryItem2, TSecondaryItem3)> interpolator,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Fuse(
|
||||
primary,
|
||||
secondary,
|
||||
interpolator,
|
||||
(p, s) => (p, s.Item1, s.Item2, s.Item3),
|
||||
primaryDeliveryPolicy,
|
||||
secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fuse with values from a secondary stream based on a specified interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem1">Type of item 1 of secondary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem2">Type of item 2 of secondary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem3">Type of item 3 of secondary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem4">Type of item 4 of secondary messages.</typeparam>
|
||||
/// <param name="primary">Primary stream.</param>
|
||||
/// <param name="secondary">Secondary stream of tuples (arity 4).</param>
|
||||
/// <param name="interpolator">Interpolator to use when fusing the streams.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <returns>Stream of fused tuple values flattened to arity 5.</returns>
|
||||
public static IProducer<(TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4)> Fuse<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4)> secondary,
|
||||
Interpolator<(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4), (TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4)> interpolator,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Fuse(
|
||||
primary,
|
||||
secondary,
|
||||
interpolator,
|
||||
(p, s) => (p, s.Item1, s.Item2, s.Item3, s.Item4),
|
||||
primaryDeliveryPolicy,
|
||||
secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fuse with values from a secondary stream based on a specified interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem1">Type of item 1 of secondary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem2">Type of item 2 of secondary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem3">Type of item 3 of secondary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem4">Type of item 4 of secondary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem5">Type of item 5 of secondary messages.</typeparam>
|
||||
/// <param name="primary">Primary stream.</param>
|
||||
/// <param name="secondary">Secondary stream of tuples (arity 5).</param>
|
||||
/// <param name="interpolator">Interpolator to use when fusing the streams.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <returns>Stream of fused tuple values flattened to arity 6.</returns>
|
||||
public static IProducer<(TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5)> Fuse<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5)> secondary,
|
||||
Interpolator<(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5), (TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5)> interpolator,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Fuse(
|
||||
primary,
|
||||
secondary,
|
||||
interpolator,
|
||||
(p, s) => (p, s.Item1, s.Item2, s.Item3, s.Item4, s.Item5),
|
||||
primaryDeliveryPolicy,
|
||||
secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fuse with values from a secondary stream based on a specified interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem1">Type of item 1 of secondary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem2">Type of item 2 of secondary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem3">Type of item 3 of secondary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem4">Type of item 4 of secondary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem5">Type of item 5 of secondary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem6">Type of item 6 of secondary messages.</typeparam>
|
||||
/// <param name="primary">Primary stream.</param>
|
||||
/// <param name="secondary">Secondary stream of tuples (arity 6).</param>
|
||||
/// <param name="interpolator">Interpolator to use when fusing the streams.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <returns>Stream of fused tuple values flattened to arity 7.</returns>
|
||||
public static IProducer<(TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6)> Fuse<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6)> secondary,
|
||||
Interpolator<(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6), (TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6)> interpolator,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Fuse(
|
||||
primary,
|
||||
secondary,
|
||||
interpolator,
|
||||
(p, s) => (p, s.Item1, s.Item2, s.Item3, s.Item4, s.Item5, s.Item6),
|
||||
primaryDeliveryPolicy,
|
||||
secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
#endregion Reverse tuple-flattening scalar fuse operators
|
||||
|
||||
#region Vector fuse operators
|
||||
|
||||
/// <summary>
|
||||
/// Fuses a primary stream with an enumeration of secondary streams based on a specified interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary stream messages.</typeparam>
|
||||
/// <typeparam name="TSecondary">Type of secondary stream messages.</typeparam>
|
||||
/// <typeparam name="TInterpolation">Type of the interpolation result.</typeparam>
|
||||
/// <typeparam name="TOut">Type of output stream messages.</typeparam>
|
||||
/// <param name="primary">Primary stream.</param>
|
||||
/// <param name="secondaries">Enumeration of secondary streams.</param>
|
||||
/// <param name="interpolator">Interpolator to use when fusing the streams.</param>
|
||||
/// <param name="outputCreator">Mapping function from primary and secondary messages to output.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondariesDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <returns>Output stream.</returns>
|
||||
public static IProducer<TOut> Fuse<TPrimary, TSecondary, TInterpolation, TOut>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IEnumerable<IProducer<TSecondary>> secondaries,
|
||||
Interpolator<TSecondary, TInterpolation> interpolator,
|
||||
Func<TPrimary, TInterpolation[], TOut> outputCreator,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondariesDeliveryPolicy = null)
|
||||
{
|
||||
var fuse = new Fuse<TPrimary, TSecondary, TInterpolation, TOut>(
|
||||
primary.Out.Pipeline,
|
||||
interpolator,
|
||||
outputCreator,
|
||||
secondaries.Count(),
|
||||
null);
|
||||
|
||||
primary.PipeTo(fuse.InPrimary, primaryDeliveryPolicy);
|
||||
|
||||
var i = 0;
|
||||
foreach (var input in secondaries)
|
||||
{
|
||||
input.PipeTo(fuse.InSecondaries[i++], secondariesDeliveryPolicy);
|
||||
}
|
||||
|
||||
return fuse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fuses an enumeration of streams into a vector stream, based on a specified interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="TIn">Type of input stream messages.</typeparam>
|
||||
/// <param name="inputs">Collection of input streams.</param>
|
||||
/// <param name="interpolator">Interpolator to use when fusing the streams.</param>
|
||||
/// <param name="deliveryPolicy">An optional delivery policy to use for the streams.</param>
|
||||
/// <returns>Output stream.</returns>
|
||||
public static IProducer<TIn[]> Fuse<TIn>(
|
||||
this IEnumerable<IProducer<TIn>> inputs,
|
||||
Interpolator<TIn, TIn> interpolator,
|
||||
DeliveryPolicy deliveryPolicy = null)
|
||||
{
|
||||
var count = inputs.Count();
|
||||
if (count > 1)
|
||||
{
|
||||
var buffer = new TIn[count];
|
||||
return Fuse(
|
||||
inputs.First(),
|
||||
inputs.Skip(1),
|
||||
interpolator,
|
||||
(m, secondaryArray) =>
|
||||
{
|
||||
buffer[0] = m;
|
||||
Array.Copy(secondaryArray, 0, buffer, 1, count - 1);
|
||||
return buffer;
|
||||
},
|
||||
deliveryPolicy,
|
||||
deliveryPolicy);
|
||||
}
|
||||
else if (count == 1)
|
||||
{
|
||||
return inputs.First().Select(x => new[] { x }, deliveryPolicy);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Vector fuse with empty inputs collection.");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Vector fuse operators
|
||||
}
|
||||
}
|
|
@ -9,140 +9,204 @@ namespace Microsoft.Psi
|
|||
using Microsoft.Psi.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Factory methods for instantiating generators.
|
||||
/// Factory methods for constructing finite stream generators.
|
||||
/// </summary>
|
||||
public static class Generators
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a stream of values from a user-provided function, at a regular interval.
|
||||
/// When the pipeline is in replay mode, the timing of the messages complies with the speed of the pipeline.
|
||||
/// Generates a finite stream of values published at a regular interval from a user-provided function.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of data in the sequence.</typeparam>
|
||||
/// <param name="pipeline">The pipeline that will run this generator.</param>
|
||||
/// <param name="initialValue">The initial value.</param>
|
||||
/// <param name="generateNext">The function that generates a new value based on the previous value.</param>
|
||||
/// <param name="count">The count of values to generate. Use int.MaxValue if the generator should never stop.</param>
|
||||
/// <param name="count">The number of messages to publish.</param>
|
||||
/// <param name="interval">The desired time interval between consecutive messages. Defaults to 1 tick.</param>
|
||||
/// <param name="alignDateTime">If non-null, this parameter specifies a time to align the generator messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with the specified time.</param>
|
||||
/// <param name="alignmentDateTime">If non-null, this parameter specifies a time to align the generator messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with (i.e., are an integral number of intervals away from) the
|
||||
/// specified alignment time.</param>
|
||||
/// <param name="keepOpen">Indicates whether the stream should be kept open after all messages in the sequence have been posted.</param>
|
||||
/// <returns>A stream of values of type T.</returns>
|
||||
public static IProducer<T> Sequence<T>(Pipeline pipeline, T initialValue, Func<T, T> generateNext, int count, TimeSpan interval = default(TimeSpan), DateTime? alignDateTime = null)
|
||||
/// <remarks>When the pipeline is in replay mode, the timing of the messages complies with the speed of the pipeline.</remarks>
|
||||
public static IProducer<T> Sequence<T>(Pipeline pipeline, T initialValue, Func<T, T> generateNext, int count, TimeSpan interval, DateTime? alignmentDateTime = null, bool keepOpen = false)
|
||||
{
|
||||
return Sequence(pipeline, Enumerate(initialValue, generateNext, count), interval, alignDateTime);
|
||||
return Sequence(pipeline, Enumerate(initialValue, generateNext, count), interval, alignmentDateTime, keepOpen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms an enumerator into a stream of messages published at regular intervals.
|
||||
/// When the pipeline is in replay mode, the timing of the messages complies with the speed of the pipeline.
|
||||
/// Generates an infinite stream of values published at a regular interval from a user-provided function.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of data in the sequence.</typeparam>
|
||||
/// <param name="pipeline">The pipeline that will run this generator.</param>
|
||||
/// <param name="initialValue">The initial value.</param>
|
||||
/// <param name="generateNext">The function that generates a new value based on the previous value.</param>
|
||||
/// <param name="interval">The desired time interval between consecutive messages. Defaults to 1 tick.</param>
|
||||
/// <param name="alignmentDateTime">If non-null, this parameter specifies a time to align the generator messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with (i.e., are an integral number of intervals away from) the
|
||||
/// specified alignment time.</param>
|
||||
/// <returns>A stream of values of type T.</returns>
|
||||
/// <remarks>When the pipeline is in replay mode, the timing of the messages complies with the speed of the pipeline.</remarks>
|
||||
public static IProducer<T> Sequence<T>(Pipeline pipeline, T initialValue, Func<T, T> generateNext, TimeSpan interval, DateTime? alignmentDateTime = null)
|
||||
{
|
||||
return Sequence(pipeline, Enumerate(initialValue, generateNext), interval, alignmentDateTime, keepOpen: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a stream of values published at a regular interval from a specified enumerator.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of data in the sequence.</typeparam>
|
||||
/// <param name="pipeline">The pipeline that will run this generator.</param>
|
||||
/// <param name="enumerator">The enumerator producing the values to publish.</param>
|
||||
/// <param name="interval">The desired time interval between consecutive messages. Defaults to 1 tick.</param>
|
||||
/// <param name="alignDateTime">If non-null, this parameter specifies a time to align the generator messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with the specified time.</param>
|
||||
/// <param name="alignmentDateTime">If non-null, this parameter specifies a time to align the generator messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with (i.e., are an integral number of intervals away from) the
|
||||
/// specified alignment time.</param>
|
||||
/// <param name="keepOpen">Indicates whether the stream should be kept open after all messages in the sequence have been posted.</param>
|
||||
/// <returns>A stream of values of type T.</returns>
|
||||
public static IProducer<T> Sequence<T>(Pipeline pipeline, IEnumerator<T> enumerator, TimeSpan interval = default(TimeSpan), DateTime? alignDateTime = null)
|
||||
/// <remarks>When the pipeline is in replay mode, the timing of the messages complies with the speed of the pipeline. The generated stream closes when the enumerator closes.</remarks>
|
||||
public static IProducer<T> Sequence<T>(Pipeline pipeline, IEnumerator<T> enumerator, TimeSpan interval, DateTime? alignmentDateTime = null, bool keepOpen = false)
|
||||
{
|
||||
var g = new Generator<T>(pipeline, enumerator, interval, alignDateTime);
|
||||
return g;
|
||||
return new Generator<T>(pipeline, enumerator, interval, alignmentDateTime, isInfiniteSource: keepOpen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms an enumerable sequence into a stream of messages published at regular intervals.
|
||||
/// When the pipeline is in replay mode, the timing of the messages complies with the speed of the pipeline.
|
||||
/// Generates a stream of values published at a regular interval from a specified enumerable.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of data in the sequence.</typeparam>
|
||||
/// <param name="pipeline">The pipeline that will run this generator.</param>
|
||||
/// <param name="enumerable">The sequence to publish.</param>
|
||||
/// <param name="interval">The desired time interval between consecutive messages. Defaults to 1 tick.</param>
|
||||
/// <param name="alignDateTime">If non-null, this parameter specifies a time to align the generator messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with the specified time.</param>
|
||||
/// <param name="alignmentDateTime">If non-null, this parameter specifies a time to align the generator messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with (i.e., are an integral number of intervals away from) the
|
||||
/// specified alignment time.</param>
|
||||
/// <param name="keepOpen">Indicates whether the stream should be kept open after all messages in the sequence have been posted.</param>
|
||||
/// <returns>A stream of values of type T.</returns>
|
||||
public static IProducer<T> Sequence<T>(Pipeline pipeline, IEnumerable<T> enumerable, TimeSpan interval = default(TimeSpan), DateTime? alignDateTime = null)
|
||||
/// <remarks>When the pipeline is in replay mode, the timing of the messages complies with the speed of the pipeline.</remarks>
|
||||
public static IProducer<T> Sequence<T>(Pipeline pipeline, IEnumerable<T> enumerable, TimeSpan interval, DateTime? alignmentDateTime = null, bool keepOpen = false)
|
||||
{
|
||||
var g = new Generator<T>(pipeline, enumerable.GetEnumerator(), interval, alignDateTime);
|
||||
return g;
|
||||
return new Generator<T>(pipeline, enumerable.GetEnumerator(), interval, alignmentDateTime, isInfiniteSource: keepOpen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a stream by enumerating a sequence of data and originating time pairs.
|
||||
/// When the pipeline is in replay mode, the timing of the messages complies with the speed of the pipeline.
|
||||
/// Generates a stream of values from a specified enumerator that provides the values and corresponding originating times.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of data in the sequence.</typeparam>
|
||||
/// <param name="pipeline">The pipeline that will run this generator.</param>
|
||||
/// <param name="enumerator">An enumerator of (data, originating time) pairs.</param>
|
||||
/// <param name="keepOpen">Indicates whether the stream should be kept open after all the messages in the enumerator have been posted.</param>
|
||||
/// <returns>A stream of values of type T.</returns>
|
||||
public static IProducer<T> Sequence<T>(Pipeline pipeline, IEnumerator<(T, DateTime)> enumerator)
|
||||
/// <remarks>When the pipeline is in replay mode, the timing of the messages complies with the speed of the pipeline.</remarks>
|
||||
public static IProducer<T> Sequence<T>(Pipeline pipeline, IEnumerator<(T, DateTime)> enumerator, bool keepOpen = false)
|
||||
{
|
||||
var g = new Generator<T>(pipeline, enumerator);
|
||||
return g;
|
||||
return new Generator<T>(pipeline, enumerator, isInfiniteSource: keepOpen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a stream by enumerating a sequence of data and originating time pairs.
|
||||
/// When the pipeline is in replay mode, the timing of the messages complies with the speed of the pipeline.
|
||||
/// Generates a stream of values from a specified enumerable that provides the values and corresponding originating times.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of data in the sequence.</typeparam>
|
||||
/// <param name="pipeline">The pipeline that will run this generator.</param>
|
||||
/// <param name="enumerable">An enumerable sequence of (data, originating time) pairs.</param>
|
||||
/// <param name="keepOpen">Indicates whether the stream should be kept open after all the messages in the enumerable have been posted.</param>
|
||||
/// <returns>A stream of values of type T.</returns>
|
||||
public static IProducer<T> Sequence<T>(Pipeline pipeline, IEnumerable<(T, DateTime)> enumerable)
|
||||
/// <remarks>When the pipeline is in replay mode, the timing of the messages complies with the speed of the pipeline.</remarks>
|
||||
public static IProducer<T> Sequence<T>(Pipeline pipeline, IEnumerable<(T, DateTime)> enumerable, bool keepOpen = false)
|
||||
{
|
||||
var g = new Generator<T>(pipeline, enumerable.GetEnumerator());
|
||||
return g;
|
||||
return new Generator<T>(pipeline, enumerable.GetEnumerator(), isInfiniteSource: keepOpen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a single message containing the specified value.
|
||||
/// Generates stream containing a single message, and keeps the stream open afterwards.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to publish.</typeparam>
|
||||
/// <param name="pipeline">The pipeline to attach to.</param>
|
||||
/// <param name="value">The value to publish.</param>
|
||||
/// <returns>A stream containing one value of type T.</returns>
|
||||
public static IProducer<T> Return<T>(Pipeline pipeline, T value)
|
||||
/// <param name="interval">The desired time interval used to align the messages with an alignment time. Defaults to 1 tick.</param>
|
||||
/// <param name="alignmentDateTime">If non-null, this parameter specifies a time to align the generator messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with (i.e., are an integral number of intervals away from) the
|
||||
/// specified alignment time.</param>
|
||||
/// <returns>A stream of values of type T.</returns>
|
||||
/// <remarks>The generated stream stays open until the pipeline is shut down.</remarks>
|
||||
public static IProducer<T> Once<T>(Pipeline pipeline, T value, TimeSpan interval = default, DateTime? alignmentDateTime = null)
|
||||
{
|
||||
return Sequence(pipeline, new[] { value });
|
||||
return Sequence(pipeline, new[] { value }, interval, alignmentDateTime, keepOpen: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a stream of messages containing the same value.
|
||||
/// When the pipeline is in replay mode, the timing of the messages complies with the speed of the pipeline.
|
||||
/// Generates stream containing a single message, and closes the stream afterwards.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to publish.</typeparam>
|
||||
/// <param name="pipeline">The pipeline to attach to.</param>
|
||||
/// <param name="value">The value to publish.</param>
|
||||
/// <param name="interval">The desired time interval between consecutive messages, or used to align the messages with an alignment time. Defaults to 1 tick.</param>
|
||||
/// <param name="alignmentDateTime">If non-null, this parameter specifies a time to align the generator messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with (i.e., are an integral number of intervals away from) the
|
||||
/// specified alignment time.</param>
|
||||
/// <returns>A stream containing one value of type T.</returns>
|
||||
/// <remarks>The generated stream closes after the message is published.</remarks>
|
||||
public static IProducer<T> Return<T>(Pipeline pipeline, T value, TimeSpan interval = default, DateTime? alignmentDateTime = null)
|
||||
{
|
||||
return Sequence(pipeline, new[] { value }, interval, alignmentDateTime, keepOpen: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a finite stream of constant values published at a regular interval.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of data in the sequence.</typeparam>
|
||||
/// <param name="pipeline">The pipeline that will run this generator.</param>
|
||||
/// <param name="value">The value to publish.</param>
|
||||
/// <param name="count">The count of values to generate. Use int.MaxValue if the generator should never stop.</param>
|
||||
/// <param name="count">The number of messages to publish.</param>
|
||||
/// <param name="interval">The desired time interval between consecutive messages. Defaults to 1 tick.</param>
|
||||
/// <param name="alignDateTime">If non-null, this parameter specifies a time to align the generator messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with the specified time.</param>
|
||||
/// <param name="alignmentDateTime">If non-null, this parameter specifies a time to align the generator messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with (i.e., are an integral number of intervals away from) the
|
||||
/// specified alignment time.</param>
|
||||
/// <param name="keepOpen">Indicates whether the stream should be kept open after the specified number of messages have been posted.</param>
|
||||
/// <returns>A stream of values of type T.</returns>
|
||||
public static IProducer<T> Repeat<T>(Pipeline pipeline, T value, int count, TimeSpan interval = default(TimeSpan), DateTime? alignDateTime = null)
|
||||
/// <remarks>When the pipeline is in replay mode, the timing of the messages complies with the speed of the pipeline. The generated stream closes once the specified number of messages has been published.</remarks>
|
||||
public static IProducer<T> Repeat<T>(Pipeline pipeline, T value, int count, TimeSpan interval, DateTime? alignmentDateTime = null, bool keepOpen = false)
|
||||
{
|
||||
return Sequence(pipeline, Enumerable.Repeat(value, count), interval, alignDateTime);
|
||||
return Sequence(pipeline, Enumerable.Repeat(value, count), interval, alignmentDateTime, keepOpen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a stream of consecutive integer values, published at regular intervals.
|
||||
/// When the pipeline is in replay mode, the timing of the messages complies with the speed of the pipeline.
|
||||
/// Generates an infinite stream of constant values published at a regular interval.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of data in the sequence.</typeparam>
|
||||
/// <param name="pipeline">The pipeline that will run this generator.</param>
|
||||
/// <param name="value">The value to publish.</param>
|
||||
/// <param name="interval">The desired time interval between consecutive messages. Defaults to 1 tick.</param>
|
||||
/// <param name="alignmentDateTime">If non-null, this parameter specifies a time to align the generator messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with (i.e., are an integral number of intervals away from) the
|
||||
/// specified alignment time.</param>
|
||||
/// <returns>A stream of values of type T.</returns>
|
||||
/// <remarks>When the pipeline is in replay mode, the timing of the messages complies with the speed of the pipeline.</remarks>
|
||||
public static IProducer<T> Repeat<T>(Pipeline pipeline, T value, TimeSpan interval, DateTime? alignmentDateTime = null)
|
||||
{
|
||||
return Sequence(pipeline, Enumerate(value, x => x), interval, alignmentDateTime, keepOpen: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a stream of a finite range of integer values published at a regular interval.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline that will run this generator.</param>
|
||||
/// <param name="start">The starting value.</param>
|
||||
/// <param name="count">The count of values to generate. Use int.MaxValue if the generator should never stop.</param>
|
||||
/// <param name="count">The number of messages to publish.</param>
|
||||
/// <param name="interval">The desired time interval between consecutive messages. Defaults to 1 tick.</param>
|
||||
/// <param name="alignDateTime">If non-null, this parameter specifies a time to align the generator messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with the specified time.</param>
|
||||
/// <returns>A stream of consecutive integers.</returns>
|
||||
public static IProducer<int> Range(Pipeline pipeline, int start, int count, TimeSpan interval = default(TimeSpan), DateTime? alignDateTime = null)
|
||||
/// <param name="keepOpen">Indicates whether the stream should be kept open after the specified number of messages have been posted.</param>
|
||||
/// <remarks>When the pipeline is in replay mode, the timing of the messages complies with the speed of the pipeline.</remarks>
|
||||
public static IProducer<int> Range(Pipeline pipeline, int start, int count, TimeSpan interval, DateTime? alignDateTime = null, bool keepOpen = false)
|
||||
{
|
||||
return Sequence(pipeline, Enumerable.Range(start, count), interval, alignDateTime);
|
||||
return Sequence(pipeline, Enumerable.Range(start, count), interval, alignDateTime, keepOpen);
|
||||
}
|
||||
|
||||
private static IEnumerable<TResult> Enumerate<TResult>(TResult initialValue, Func<TResult, TResult> generateNext, int count)
|
||||
internal static IEnumerable<TResult> Enumerate<TResult>(TResult initialValue, Func<TResult, TResult> generateNext, int count)
|
||||
{
|
||||
if (count < 0)
|
||||
{
|
||||
throw new ArgumentException("count");
|
||||
throw new ArgumentException("The count parameter has to be positive.");
|
||||
}
|
||||
|
||||
yield return initialValue;
|
||||
|
@ -153,5 +217,16 @@ namespace Microsoft.Psi
|
|||
yield return value;
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerable<TResult> Enumerate<TResult>(TResult initialValue, Func<TResult, TResult> generateNext)
|
||||
{
|
||||
yield return initialValue;
|
||||
var value = initialValue;
|
||||
while (true)
|
||||
{
|
||||
value = generateNext(value);
|
||||
yield return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi
|
||||
{
|
||||
using System;
|
||||
using Microsoft.Psi.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods that simplify operator usage.
|
||||
/// </summary>
|
||||
public static partial class Operators
|
||||
{
|
||||
/// <summary>
|
||||
/// Interpolate a stream using a specified interpolator at a given sampling interval.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of source messages.</typeparam>
|
||||
/// <typeparam name="TInterpolation">Type of the interpolation result.</typeparam>
|
||||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="samplingInterval">Interval at which to apply the interpolator.</param>
|
||||
/// <param name="interpolator">Interpolator to use for generating results.</param>
|
||||
/// <param name="alignmentDateTime">If non-null, this parameter specifies a time to align the sampling messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with (i.e., are an integral number of intervals away from) the
|
||||
/// specified alignment time.</param>
|
||||
/// <param name="deliveryPolicy">An optional delivery policy.</param>
|
||||
/// <returns>Output stream.</returns>
|
||||
public static IProducer<TInterpolation> Interpolate<T, TInterpolation>(
|
||||
this IProducer<T> source,
|
||||
TimeSpan samplingInterval,
|
||||
Interpolator<T, TInterpolation> interpolator,
|
||||
DateTime? alignmentDateTime = null,
|
||||
DeliveryPolicy deliveryPolicy = null)
|
||||
{
|
||||
var clock = Generators.Repeat(source.Out.Pipeline, 0, samplingInterval, alignmentDateTime);
|
||||
return source.Interpolate(clock, interpolator, deliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interpolate a stream using a specified interpolator at interpolation points
|
||||
/// given by a clock stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of source messages.</typeparam>
|
||||
/// <typeparam name="TClock">Type of messages on the clock stream.</typeparam>
|
||||
/// <typeparam name="TInterpolation">Type of the interpolation result.</typeparam>
|
||||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="clock">Clock stream that dictates the interpolation points.</param>
|
||||
/// <param name="interpolator">Interpolator to use for generating results.</param>
|
||||
/// <param name="sourceDeliveryPolicy">An optional delivery policy for the source stream.</param>
|
||||
/// <param name="clockDeliveryPolicy">An optional delivery policy for the clock stream.</param>
|
||||
/// <returns>Output stream.</returns>
|
||||
public static IProducer<TInterpolation> Interpolate<T, TClock, TInterpolation>(
|
||||
this IProducer<T> source,
|
||||
IProducer<TClock> clock,
|
||||
Interpolator<T, TInterpolation> interpolator,
|
||||
DeliveryPolicy sourceDeliveryPolicy = null,
|
||||
DeliveryPolicy clockDeliveryPolicy = null)
|
||||
{
|
||||
var fuse = new Fuse<TClock, T, TInterpolation, TInterpolation>(source.Out.Pipeline, interpolator, (clk, data) => data[0]);
|
||||
clock.PipeTo(fuse.InPrimary, clockDeliveryPolicy);
|
||||
source.PipeTo(fuse.InSecondaries[0], sourceDeliveryPolicy);
|
||||
return fuse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample a stream at a given sampling interval, by selecting the nearest message
|
||||
/// within a given tolerance to the interpolation point.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of source (and output) messages.</typeparam>
|
||||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="samplingInterval">Interval at which to apply the interpolator.</param>
|
||||
/// <param name="tolerance">The tolerance within which to search for the nearest message.</param>
|
||||
/// <param name="alignmentDateTime">If non-null, this parameter specifies a time to align the sampling messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with (i.e., are an integral number of intervals away from) the
|
||||
/// specified alignment time.</param>
|
||||
/// <param name="deliveryPolicy">An optional delivery policy.</param>
|
||||
/// <returns>Sampled stream.</returns>
|
||||
public static IProducer<T> Sample<T>(
|
||||
this IProducer<T> source,
|
||||
TimeSpan samplingInterval,
|
||||
TimeSpan tolerance,
|
||||
DateTime? alignmentDateTime = null,
|
||||
DeliveryPolicy deliveryPolicy = null)
|
||||
{
|
||||
return source.Interpolate(
|
||||
samplingInterval,
|
||||
Reproducible.Nearest<T>(new RelativeTimeInterval(-tolerance, tolerance)),
|
||||
alignmentDateTime,
|
||||
deliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample a stream at a given sampling interval, by selecting the nearest message
|
||||
/// within a relative time interval to the interpolation point.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of source (and output) messages.</typeparam>
|
||||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="samplingInterval">Interval at which to apply the interpolator.</param>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the nearest message.</param>
|
||||
/// <param name="alignmentDateTime">If non-null, this parameter specifies a time to align the sampling messages with. If the paramater
|
||||
/// is non-null, the messages will have originating times that align with (i.e., are an integral number of intervals away from) the
|
||||
/// specified alignment time.</param>
|
||||
/// <param name="deliveryPolicy">An optional delivery policy.</param>
|
||||
/// <returns>Sampled stream.</returns>
|
||||
public static IProducer<T> Sample<T>(
|
||||
this IProducer<T> source,
|
||||
TimeSpan samplingInterval,
|
||||
RelativeTimeInterval relativeTimeInterval,
|
||||
DateTime? alignmentDateTime = null,
|
||||
DeliveryPolicy deliveryPolicy = null)
|
||||
{
|
||||
return source.Interpolate(
|
||||
samplingInterval,
|
||||
Reproducible.Nearest<T>(relativeTimeInterval),
|
||||
alignmentDateTime,
|
||||
deliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample a stream at interpolation points given by a clock stream, by selecting the nearest
|
||||
/// message within a given tolerance to the interpolation point.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of source and output messages.</typeparam>
|
||||
/// <typeparam name="TClock">Type of messages on the clock stream.</typeparam>
|
||||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="clock">Clock stream that dictates the interpolation points.</param>
|
||||
/// <param name="tolerance">The tolerance within which to search for the nearest message.</param>
|
||||
/// <param name="sourceDeliveryPolicy">An optional delivery policy for the source stream.</param>
|
||||
/// <param name="clockDeliveryPolicy">An optional delivery policy for the clock stream.</param>
|
||||
/// <returns>Sampled stream.</returns>
|
||||
public static IProducer<T> Sample<T, TClock>(
|
||||
this IProducer<T> source,
|
||||
IProducer<TClock> clock,
|
||||
TimeSpan tolerance,
|
||||
DeliveryPolicy sourceDeliveryPolicy = null,
|
||||
DeliveryPolicy clockDeliveryPolicy = null)
|
||||
{
|
||||
return source.Interpolate(clock, Reproducible.Nearest<T>(new RelativeTimeInterval(-tolerance, tolerance)), sourceDeliveryPolicy, clockDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Samples a stream at interpolation points given by a clock stream, by selecting the nearest
|
||||
/// message within a relative time interval to the interpolation point.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of source and output messages.</typeparam>
|
||||
/// <typeparam name="TClock">Type of messages on the clock stream.</typeparam>
|
||||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="clock">Clock stream that dictates the interpolation points.</param>
|
||||
/// <param name="relativeTimeInterval">The relative time interval within which to search for the nearest message.</param>
|
||||
/// <param name="sourceDeliveryPolicy">An optional delivery policy for the source stream.</param>
|
||||
/// <param name="clockDeliveryPolicy">An optional delivery policy for the clock stream.</param>
|
||||
/// <returns>Sampled stream.</returns>
|
||||
public static IProducer<T> Sample<T, TClock>(
|
||||
this IProducer<T> source,
|
||||
IProducer<TClock> clock,
|
||||
RelativeTimeInterval relativeTimeInterval,
|
||||
DeliveryPolicy sourceDeliveryPolicy = null,
|
||||
DeliveryPolicy clockDeliveryPolicy = null)
|
||||
{
|
||||
return source.Interpolate(clock, Reproducible.Nearest<T>(relativeTimeInterval), sourceDeliveryPolicy, clockDeliveryPolicy);
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -11,10 +11,10 @@ namespace Microsoft.Psi
|
|||
/// </summary>
|
||||
public static partial class Operators
|
||||
{
|
||||
#region scalar pairs
|
||||
#region Scalar pairs
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondary">Type of secondary messages.</typeparam>
|
||||
|
@ -25,19 +25,17 @@ namespace Microsoft.Psi
|
|||
/// <param name="initialValue">An initial value to be used until the first secondary message is received.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired values.</returns>
|
||||
/// <returns>Stream of output values.</returns>
|
||||
public static IProducer<TOut> Pair<TPrimary, TSecondary, TOut>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
Func<TPrimary, TSecondary, TOut> outputCreator,
|
||||
TSecondary initialValue,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(
|
||||
new Pair<TPrimary, TSecondary, TOut>(pipeline ?? primary.Out.Pipeline, outputCreator, initialValue),
|
||||
new Pair<TPrimary, TSecondary, TOut>(primary.Out.Pipeline, outputCreator, initialValue),
|
||||
primary,
|
||||
secondary,
|
||||
primaryDeliveryPolicy,
|
||||
|
@ -45,7 +43,7 @@ namespace Microsoft.Psi
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondary">Type of secondary messages.</typeparam>
|
||||
|
@ -55,19 +53,17 @@ namespace Microsoft.Psi
|
|||
/// <param name="outputCreator">Mapping function from primary/secondary pairs to output type.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <remarks>Primary messages will be dropped until the first secondary message is received (no `initialValue` provided).</remarks>
|
||||
/// <returns>Stream of paired values.</returns>
|
||||
/// <returns>Stream of output values.</returns>
|
||||
public static IProducer<TOut> Pair<TPrimary, TSecondary, TOut>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
Func<TPrimary, TSecondary, TOut> outputCreator,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(
|
||||
new Pair<TPrimary, TSecondary, TOut>(pipeline ?? primary.Out.Pipeline, outputCreator),
|
||||
new Pair<TPrimary, TSecondary, TOut>(primary.Out.Pipeline, outputCreator),
|
||||
primary,
|
||||
secondary,
|
||||
primaryDeliveryPolicy,
|
||||
|
@ -75,7 +71,7 @@ namespace Microsoft.Psi
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondary">Type of secondary messages.</typeparam>
|
||||
|
@ -84,18 +80,16 @@ namespace Microsoft.Psi
|
|||
/// <param name="initialValue">An initial value to be used until the first secondary message is received.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values.</returns>
|
||||
public static IProducer<ValueTuple<TPrimary, TSecondary>> Pair<TPrimary, TSecondary>(
|
||||
/// <returns>Stream of output tuples.</returns>
|
||||
public static IProducer<(TPrimary, TSecondary)> Pair<TPrimary, TSecondary>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
TSecondary initialValue,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(
|
||||
new Pair<TPrimary, TSecondary, ValueTuple<TPrimary, TSecondary>>(pipeline ?? primary.Out.Pipeline, ValueTuple.Create, initialValue),
|
||||
new Pair<TPrimary, TSecondary, (TPrimary, TSecondary)>(primary.Out.Pipeline, ValueTuple.Create, initialValue),
|
||||
primary,
|
||||
secondary,
|
||||
primaryDeliveryPolicy,
|
||||
|
@ -103,7 +97,7 @@ namespace Microsoft.Psi
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondary">Type of secondary messages.</typeparam>
|
||||
|
@ -111,30 +105,28 @@ namespace Microsoft.Psi
|
|||
/// <param name="secondary">Secondary stream.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <remarks>Primary messages will be dropped until the first secondary message is received (no `initialValue` provided).</remarks>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values.</returns>
|
||||
public static IProducer<ValueTuple<TPrimary, TSecondary>> Pair<TPrimary, TSecondary>(
|
||||
/// <returns>Stream of output tuples.</returns>
|
||||
public static IProducer<(TPrimary, TSecondary)> Pair<TPrimary, TSecondary>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(
|
||||
new Pair<TPrimary, TSecondary, ValueTuple<TPrimary, TSecondary>>(pipeline ?? primary.Out.Pipeline, ValueTuple.Create),
|
||||
new Pair<TPrimary, TSecondary, (TPrimary, TSecondary)>(primary.Out.Pipeline, ValueTuple.Create),
|
||||
primary,
|
||||
secondary,
|
||||
primaryDeliveryPolicy,
|
||||
secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
#endregion scalar pairs
|
||||
#endregion Scalar pairs
|
||||
|
||||
#region tuple-flattening scalar pairs
|
||||
#region Tuple-flattening scalar pairs
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimaryItem1">Type of item 1 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem2">Type of item 2 of primary messages.</typeparam>
|
||||
|
@ -144,21 +136,19 @@ namespace Microsoft.Psi
|
|||
/// <param name="initialValue">An initial value to be used until the first secondary message is received.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 3.</returns>
|
||||
public static IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TSecondary>> Pair<TPrimaryItem1, TPrimaryItem2, TSecondary>(
|
||||
this IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2>> primary,
|
||||
/// <returns>Stream of output tuples flattened to arity 3.</returns>
|
||||
public static IProducer<(TPrimaryItem1, TPrimaryItem2, TSecondary)> Pair<TPrimaryItem1, TPrimaryItem2, TSecondary>(
|
||||
this IProducer<(TPrimaryItem1, TPrimaryItem2)> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
TSecondary initialValue,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p.Item1, p.Item2, s), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p.Item1, p.Item2, s), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimaryItem1">Type of item 1 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem2">Type of item 2 of primary messages.</typeparam>
|
||||
|
@ -167,20 +157,18 @@ namespace Microsoft.Psi
|
|||
/// <param name="secondary">Secondary stream.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 3.</returns>
|
||||
public static IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TSecondary>> Pair<TPrimaryItem1, TPrimaryItem2, TSecondary>(
|
||||
this IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2>> primary,
|
||||
/// <returns>Stream of output tuples flattened to arity 3.</returns>
|
||||
public static IProducer<(TPrimaryItem1, TPrimaryItem2, TSecondary)> Pair<TPrimaryItem1, TPrimaryItem2, TSecondary>(
|
||||
this IProducer<(TPrimaryItem1, TPrimaryItem2)> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p.Item1, p.Item2, s), primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p.Item1, p.Item2, s), primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimaryItem1">Type of item 1 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem2">Type of item 2 of primary messages.</typeparam>
|
||||
|
@ -191,21 +179,19 @@ namespace Microsoft.Psi
|
|||
/// <param name="initialValue">An initial value to be used until the first secondary message is received.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 4.</returns>
|
||||
public static IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TSecondary>> Pair<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TSecondary>(
|
||||
this IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3>> primary,
|
||||
/// <returns>Stream of output tuples flattened to arity 4.</returns>
|
||||
public static IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TSecondary)> Pair<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TSecondary>(
|
||||
this IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3)> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
TSecondary initialValue,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p.Item1, p.Item2, p.Item3, s), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p.Item1, p.Item2, p.Item3, s), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimaryItem1">Type of item 1 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem2">Type of item 2 of primary messages.</typeparam>
|
||||
|
@ -215,20 +201,18 @@ namespace Microsoft.Psi
|
|||
/// <param name="secondary">Secondary stream.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 4.</returns>
|
||||
public static IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TSecondary>> Pair<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TSecondary>(
|
||||
this IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3>> primary,
|
||||
/// <returns>Stream of output tuples flattened to arity 4.</returns>
|
||||
public static IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TSecondary)> Pair<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TSecondary>(
|
||||
this IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3)> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p.Item1, p.Item2, p.Item3, s), primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p.Item1, p.Item2, p.Item3, s), primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimaryItem1">Type of item 1 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem2">Type of item 2 of primary messages.</typeparam>
|
||||
|
@ -240,21 +224,19 @@ namespace Microsoft.Psi
|
|||
/// <param name="initialValue">An initial value to be used until the first secondary message is received.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 5.</returns>
|
||||
public static IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TSecondary>> Pair<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TSecondary>(
|
||||
this IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4>> primary,
|
||||
/// <returns>Stream of output tuples flattened to arity 5.</returns>
|
||||
public static IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TSecondary)> Pair<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TSecondary>(
|
||||
this IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4)> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
TSecondary initialValue,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p.Item1, p.Item2, p.Item3, p.Item4, s), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p.Item1, p.Item2, p.Item3, p.Item4, s), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimaryItem1">Type of item 1 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem2">Type of item 2 of primary messages.</typeparam>
|
||||
|
@ -265,20 +247,18 @@ namespace Microsoft.Psi
|
|||
/// <param name="secondary">Secondary stream.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 5.</returns>
|
||||
public static IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TSecondary>> Pair<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TSecondary>(
|
||||
this IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4>> primary,
|
||||
/// <returns>Stream of output tuples flattened to arity 5.</returns>
|
||||
public static IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TSecondary)> Pair<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TSecondary>(
|
||||
this IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4)> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p.Item1, p.Item2, p.Item3, p.Item4, s), primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p.Item1, p.Item2, p.Item3, p.Item4, s), primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimaryItem1">Type of item 1 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem2">Type of item 2 of primary messages.</typeparam>
|
||||
|
@ -291,21 +271,19 @@ namespace Microsoft.Psi
|
|||
/// <param name="initialValue">An initial value to be used until the first secondary message is received.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 6.</returns>
|
||||
public static IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TSecondary>> Pair<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TSecondary>(
|
||||
this IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5>> primary,
|
||||
/// <returns>Stream of output tuples flattened to arity 6.</returns>
|
||||
public static IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TSecondary)> Pair<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TSecondary>(
|
||||
this IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5)> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
TSecondary initialValue,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p.Item1, p.Item2, p.Item3, p.Item4, p.Item5, s), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p.Item1, p.Item2, p.Item3, p.Item4, p.Item5, s), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimaryItem1">Type of item 1 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem2">Type of item 2 of primary messages.</typeparam>
|
||||
|
@ -317,20 +295,18 @@ namespace Microsoft.Psi
|
|||
/// <param name="secondary">Secondary stream.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 6.</returns>
|
||||
public static IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TSecondary>> Pair<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TSecondary>(
|
||||
this IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5>> primary,
|
||||
/// <returns>Stream of output tuples flattened to arity 6.</returns>
|
||||
public static IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TSecondary)> Pair<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TSecondary>(
|
||||
this IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5)> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p.Item1, p.Item2, p.Item3, p.Item4, p.Item5, s), primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p.Item1, p.Item2, p.Item3, p.Item4, p.Item5, s), primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimaryItem1">Type of item 1 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem2">Type of item 2 of primary messages.</typeparam>
|
||||
|
@ -344,21 +320,19 @@ namespace Microsoft.Psi
|
|||
/// <param name="initialValue">An initial value to be used until the first secondary message is received.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 7.</returns>
|
||||
public static IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TPrimaryItem6, TSecondary>> Pair<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TPrimaryItem6, TSecondary>(
|
||||
this IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TPrimaryItem6>> primary,
|
||||
/// <returns>Stream of output tuples flattened to arity 7.</returns>
|
||||
public static IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TPrimaryItem6, TSecondary)> Pair<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TPrimaryItem6, TSecondary>(
|
||||
this IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TPrimaryItem6)> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
TSecondary initialValue,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p.Item1, p.Item2, p.Item3, p.Item4, p.Item5, p.Item6, s), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p.Item1, p.Item2, p.Item3, p.Item4, p.Item5, p.Item6, s), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimaryItem1">Type of item 1 of primary messages.</typeparam>
|
||||
/// <typeparam name="TPrimaryItem2">Type of item 2 of primary messages.</typeparam>
|
||||
|
@ -371,24 +345,22 @@ namespace Microsoft.Psi
|
|||
/// <param name="secondary">Secondary stream.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 7.</returns>
|
||||
public static IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TPrimaryItem6, TSecondary>> Pair<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TPrimaryItem6, TSecondary>(
|
||||
this IProducer<ValueTuple<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TPrimaryItem6>> primary,
|
||||
/// <returns>Stream of output tuples flattened to arity 7.</returns>
|
||||
public static IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TPrimaryItem6, TSecondary)> Pair<TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TPrimaryItem6, TSecondary>(
|
||||
this IProducer<(TPrimaryItem1, TPrimaryItem2, TPrimaryItem3, TPrimaryItem4, TPrimaryItem5, TPrimaryItem6)> primary,
|
||||
IProducer<TSecondary> secondary,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p.Item1, p.Item2, p.Item3, p.Item4, p.Item5, p.Item6, s), primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p.Item1, p.Item2, p.Item3, p.Item4, p.Item5, p.Item6, s), primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
#endregion tuple-flattening scalar pairs
|
||||
#endregion Tuple-flattening scalar pairs
|
||||
|
||||
#region reverse tuple-flattening scalar pairs
|
||||
#region Reverse tuple-flattening scalar pairs
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem1">Type of item 1 of secondary messages.</typeparam>
|
||||
|
@ -398,21 +370,19 @@ namespace Microsoft.Psi
|
|||
/// <param name="initialValue">An initial value to be used until the first secondary message is received.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 3.</returns>
|
||||
public static IProducer<ValueTuple<TPrimary, TSecondaryItem1, TSecondaryItem2>> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2>(
|
||||
/// <returns>Stream of output tuples flattened to arity 3.</returns>
|
||||
public static IProducer<(TPrimary, TSecondaryItem1, TSecondaryItem2)> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<ValueTuple<TSecondaryItem1, TSecondaryItem2>> secondary,
|
||||
ValueTuple<TSecondaryItem1, TSecondaryItem2> initialValue,
|
||||
IProducer<(TSecondaryItem1, TSecondaryItem2)> secondary,
|
||||
(TSecondaryItem1, TSecondaryItem2) initialValue,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p, s.Item1, s.Item2), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p, s.Item1, s.Item2), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem1">Type of item 1 of secondary messages.</typeparam>
|
||||
|
@ -421,20 +391,18 @@ namespace Microsoft.Psi
|
|||
/// <param name="secondary">Secondary stream of tuples (arity 2).</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 3.</returns>
|
||||
public static IProducer<ValueTuple<TPrimary, TSecondaryItem1, TSecondaryItem2>> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2>(
|
||||
/// <returns>Stream of output tuples flattened to arity 3.</returns>
|
||||
public static IProducer<(TPrimary, TSecondaryItem1, TSecondaryItem2)> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<ValueTuple<TSecondaryItem1, TSecondaryItem2>> secondary,
|
||||
IProducer<(TSecondaryItem1, TSecondaryItem2)> secondary,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p, s.Item1, s.Item2), primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p, s.Item1, s.Item2), primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem1">Type of item 1 of secondary messages.</typeparam>
|
||||
|
@ -445,21 +413,19 @@ namespace Microsoft.Psi
|
|||
/// <param name="initialValue">An initial value to be used until the first secondary message is received.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 4.</returns>
|
||||
public static IProducer<ValueTuple<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3>> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3>(
|
||||
/// <returns>Stream of output tuples flattened to arity 4.</returns>
|
||||
public static IProducer<(TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3)> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<ValueTuple<TSecondaryItem1, TSecondaryItem2, TSecondaryItem3>> secondary,
|
||||
ValueTuple<TSecondaryItem1, TSecondaryItem2, TSecondaryItem3> initialValue,
|
||||
IProducer<(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3)> secondary,
|
||||
(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3) initialValue,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p, s.Item1, s.Item2, s.Item3), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p, s.Item1, s.Item2, s.Item3), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem1">Type of item 1 of secondary messages.</typeparam>
|
||||
|
@ -469,20 +435,18 @@ namespace Microsoft.Psi
|
|||
/// <param name="secondary">Secondary stream of tuples (arity 3).</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 4.</returns>
|
||||
public static IProducer<ValueTuple<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3>> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3>(
|
||||
/// <returns>Stream of output tuples flattened to arity 4.</returns>
|
||||
public static IProducer<(TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3)> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<ValueTuple<TSecondaryItem1, TSecondaryItem2, TSecondaryItem3>> secondary,
|
||||
IProducer<(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3)> secondary,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p, s.Item1, s.Item2, s.Item3), primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p, s.Item1, s.Item2, s.Item3), primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem1">Type of item 1 of secondary messages.</typeparam>
|
||||
|
@ -494,21 +458,19 @@ namespace Microsoft.Psi
|
|||
/// <param name="initialValue">An initial value to be used until the first secondary message is received.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 5.</returns>
|
||||
public static IProducer<ValueTuple<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4>> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4>(
|
||||
/// <returns>Stream of output tuples flattened to arity 5.</returns>
|
||||
public static IProducer<(TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4)> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<ValueTuple<TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4>> secondary,
|
||||
ValueTuple<TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4> initialValue,
|
||||
IProducer<(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4)> secondary,
|
||||
(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4) initialValue,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p, s.Item1, s.Item2, s.Item3, s.Item4), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p, s.Item1, s.Item2, s.Item3, s.Item4), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem1">Type of item 1 of secondary messages.</typeparam>
|
||||
|
@ -519,20 +481,18 @@ namespace Microsoft.Psi
|
|||
/// <param name="secondary">Secondary stream of tuples (arity 4).</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 5.</returns>
|
||||
public static IProducer<ValueTuple<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4>> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4>(
|
||||
/// <returns>Stream of output tuples flattened to arity 5.</returns>
|
||||
public static IProducer<(TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4)> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<ValueTuple<TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4>> secondary,
|
||||
IProducer<(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4)> secondary,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p, s.Item1, s.Item2, s.Item3, s.Item4), primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p, s.Item1, s.Item2, s.Item3, s.Item4), primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem1">Type of item 1 of secondary messages.</typeparam>
|
||||
|
@ -545,21 +505,19 @@ namespace Microsoft.Psi
|
|||
/// <param name="initialValue">An initial value to be used until the first secondary message is received.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 6.</returns>
|
||||
public static IProducer<ValueTuple<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5>> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5>(
|
||||
/// <returns>Stream of output tuples flattened to arity 6.</returns>
|
||||
public static IProducer<(TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5)> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<ValueTuple<TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5>> secondary,
|
||||
ValueTuple<TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5> initialValue,
|
||||
IProducer<(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5)> secondary,
|
||||
(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5) initialValue,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p, s.Item1, s.Item2, s.Item3, s.Item4, s.Item5), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p, s.Item1, s.Item2, s.Item3, s.Item4, s.Item5), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem1">Type of item 1 of secondary messages.</typeparam>
|
||||
|
@ -571,20 +529,18 @@ namespace Microsoft.Psi
|
|||
/// <param name="secondary">Secondary stream of tuples (arity 5).</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 6.</returns>
|
||||
public static IProducer<ValueTuple<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5>> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5>(
|
||||
/// <returns>Stream of output tuples flattened to arity 6.</returns>
|
||||
public static IProducer<(TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5)> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<ValueTuple<TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5>> secondary,
|
||||
IProducer<(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5)> secondary,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p, s.Item1, s.Item2, s.Item3, s.Item4, s.Item5), primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p, s.Item1, s.Item2, s.Item3, s.Item4, s.Item5), primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem1">Type of item 1 of secondary messages.</typeparam>
|
||||
|
@ -598,21 +554,19 @@ namespace Microsoft.Psi
|
|||
/// <param name="initialValue">An initial value to be used until the first secondary message is received.</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 7.</returns>
|
||||
public static IProducer<ValueTuple<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6>> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6>(
|
||||
/// <returns>Stream of output tuples flattened to arity 7.</returns>
|
||||
public static IProducer<(TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6)> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<ValueTuple<TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6>> secondary,
|
||||
ValueTuple<TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6> initialValue,
|
||||
IProducer<(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6)> secondary,
|
||||
(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6) initialValue,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p, s.Item1, s.Item2, s.Item3, s.Item4, s.Item5, s.Item6), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p, s.Item1, s.Item2, s.Item3, s.Item4, s.Item5, s.Item6), initialValue, primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pair with latest (in wall-clock sense) values from a secondary stream.
|
||||
/// Pair with currently available value from a secondary stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary messages.</typeparam>
|
||||
/// <typeparam name="TSecondaryItem1">Type of item 1 of secondary messages.</typeparam>
|
||||
|
@ -625,19 +579,17 @@ namespace Microsoft.Psi
|
|||
/// <param name="secondary">Secondary stream of tuples (arity 6).</param>
|
||||
/// <param name="primaryDeliveryPolicy">An optional delivery policy for the primary stream.</param>
|
||||
/// <param name="secondaryDeliveryPolicy">An optional delivery policy for the secondary stream(s).</param>
|
||||
/// <param name="pipeline">The pipeline to which this component belongs (optional, defaults to that of the primary stream).</param>
|
||||
/// <returns>Stream of paired (`ValueTuple`) values flattened to arity 7.</returns>
|
||||
public static IProducer<ValueTuple<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6>> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6>(
|
||||
/// <returns>Stream of output tuples flattened to arity 7.</returns>
|
||||
public static IProducer<(TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6)> Pair<TPrimary, TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6>(
|
||||
this IProducer<TPrimary> primary,
|
||||
IProducer<ValueTuple<TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6>> secondary,
|
||||
IProducer<(TSecondaryItem1, TSecondaryItem2, TSecondaryItem3, TSecondaryItem4, TSecondaryItem5, TSecondaryItem6)> secondary,
|
||||
DeliveryPolicy primaryDeliveryPolicy = null,
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null,
|
||||
Pipeline pipeline = null)
|
||||
DeliveryPolicy secondaryDeliveryPolicy = null)
|
||||
{
|
||||
return Pair(primary, secondary, (p, s) => ValueTuple.Create(p, s.Item1, s.Item2, s.Item3, s.Item4, s.Item5, s.Item6), primaryDeliveryPolicy, secondaryDeliveryPolicy, pipeline);
|
||||
return Pair(primary, secondary, (p, s) => (p, s.Item1, s.Item2, s.Item3, s.Item4, s.Item5, s.Item6), primaryDeliveryPolicy, secondaryDeliveryPolicy);
|
||||
}
|
||||
|
||||
#endregion reverse tuple-flattening scalar pairs
|
||||
#endregion Reverse tuple-flattening scalar pairs
|
||||
|
||||
private static IProducer<TOut> Pair<TPrimary, TSecondary, TOut>(
|
||||
Pair<TPrimary, TSecondary, TOut> pair,
|
||||
|
|
|
@ -1,135 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Psi
|
||||
{
|
||||
using System;
|
||||
using Microsoft.Psi.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods that simplify operator usage.
|
||||
/// </summary>
|
||||
public static partial class Operators
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample stream by interval with an interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of source/output messages.</typeparam>
|
||||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="samplingInterval">Interval at which to sample.</param>
|
||||
/// <param name="interpolator">Interpolator with which to sample.</param>
|
||||
/// <param name="deliveryPolicy">An optional delivery policy.</param>
|
||||
/// <returns>Output stream.</returns>
|
||||
public static IProducer<T> Sample<T>(
|
||||
this IProducer<T> source,
|
||||
TimeSpan samplingInterval,
|
||||
Match.Interpolator<T> interpolator,
|
||||
DeliveryPolicy deliveryPolicy = null)
|
||||
{
|
||||
var sampler = new Sampler<T>(source.Out.Pipeline, interpolator, samplingInterval);
|
||||
return PipeTo(source, sampler, deliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample stream by interval with a time window.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of source/output messages.</typeparam>
|
||||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="samplingInterval">Interval at which to sample.</param>
|
||||
/// <param name="matchTolerance">Match tolerance with which to sample.</param>
|
||||
/// <param name="deliveryPolicy">An optional delivery policy.</param>
|
||||
/// <returns>Output stream.</returns>
|
||||
public static IProducer<T> Sample<T>(
|
||||
this IProducer<T> source,
|
||||
TimeSpan samplingInterval,
|
||||
TimeSpan matchTolerance = default(TimeSpan),
|
||||
DeliveryPolicy deliveryPolicy = null)
|
||||
{
|
||||
return Sample(source, samplingInterval, new RelativeTimeInterval(-matchTolerance, matchTolerance), deliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample stream by interval with a relative time interval window.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of source/output messages.</typeparam>
|
||||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="samplingInterval">Interval at which to sample.</param>
|
||||
/// <param name="matchWindow">Relative time interval window in which to sample.</param>
|
||||
/// <param name="deliveryPolicy">An optional delivery policy.</param>
|
||||
/// <returns>Output stream.</returns>
|
||||
public static IProducer<T> Sample<T>(
|
||||
this IProducer<T> source,
|
||||
TimeSpan samplingInterval,
|
||||
RelativeTimeInterval matchWindow,
|
||||
DeliveryPolicy deliveryPolicy = null)
|
||||
{
|
||||
return Sample(source, samplingInterval, Match.Best<T>(matchWindow), deliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample stream by clock signal with a time window.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of source/output messages.</typeparam>
|
||||
/// <typeparam name="TClock">Type of clock signal messages.</typeparam>
|
||||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="clock">Clock signal stream.</param>
|
||||
/// <param name="tolerance">Time span tolerance in which to sample.</param>
|
||||
/// <param name="sourceDeliveryPolicy">An optional delivery policy for the source.</param>
|
||||
/// <param name="clockDeliveryPolicy">An optional delivery policy for the clock.</param>
|
||||
/// <returns>Output stream.</returns>
|
||||
public static IProducer<T> Sample<T, TClock>(
|
||||
this IProducer<T> source,
|
||||
IProducer<TClock> clock,
|
||||
TimeSpan tolerance,
|
||||
DeliveryPolicy sourceDeliveryPolicy = null,
|
||||
DeliveryPolicy clockDeliveryPolicy = null)
|
||||
{
|
||||
return Sample(source, clock, new RelativeTimeInterval(-tolerance, tolerance), sourceDeliveryPolicy, clockDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample stream by clock signal with a relative time interval window.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of source/output messages.</typeparam>
|
||||
/// <typeparam name="TClock">Type of clock signal messages.</typeparam>
|
||||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="clock">Clock signal stream.</param>
|
||||
/// <param name="matchWindow">Relative time interval window in which to sample.</param>
|
||||
/// <param name="sourceDeliveryPolicy">An optional delivery policy for the source.</param>
|
||||
/// <param name="clockDeliveryPolicy">An optional delivery policy for the clock.</param>
|
||||
/// <returns>Output stream.</returns>
|
||||
public static IProducer<T> Sample<T, TClock>(
|
||||
this IProducer<T> source,
|
||||
IProducer<TClock> clock,
|
||||
RelativeTimeInterval matchWindow,
|
||||
DeliveryPolicy sourceDeliveryPolicy = null,
|
||||
DeliveryPolicy clockDeliveryPolicy = null)
|
||||
{
|
||||
return Sample(source, clock, Match.Best<T>(matchWindow), sourceDeliveryPolicy, clockDeliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample stream by clock signal with an interpolator.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of source/output messages.</typeparam>
|
||||
/// <typeparam name="TClock">Type of clock signal messages.</typeparam>
|
||||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="clock">Clock signal stream.</param>
|
||||
/// <param name="interpolator">Interpolator with which to sample.</param>
|
||||
/// <param name="sourceDeliveryPolicy">An optional delivery policy for the source.</param>
|
||||
/// <param name="clockDeliveryPolicy">An optional delivery policy for the clock.</param>
|
||||
/// <returns>Output stream.</returns>
|
||||
public static IProducer<T> Sample<T, TClock>(
|
||||
this IProducer<T> source,
|
||||
IProducer<TClock> clock,
|
||||
Match.Interpolator<T> interpolator,
|
||||
DeliveryPolicy sourceDeliveryPolicy = null,
|
||||
DeliveryPolicy clockDeliveryPolicy = null)
|
||||
{
|
||||
var join = new Join<TClock, T, T>(source.Out.Pipeline, interpolator, (clk, data) => data[0]);
|
||||
clock.PipeTo(join.InPrimary, clockDeliveryPolicy);
|
||||
source.PipeTo(join.InSecondaries[0], sourceDeliveryPolicy);
|
||||
return join;
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -4,7 +4,6 @@
|
|||
namespace Microsoft.Psi
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Psi.Components;
|
||||
|
||||
/// <summary>
|
||||
|
@ -14,7 +13,7 @@ namespace Microsoft.Psi
|
|||
{
|
||||
/// <summary>
|
||||
/// Generates a stream by invoking a user-provided function at a regular time interval.
|
||||
/// Unlike <see cref="Generators.Sequence{T}(Pipeline, IEnumerable{T}, TimeSpan, DateTime?)"/>, <see cref="Generators.Repeat{T}(Pipeline, T, int, TimeSpan, DateTime?)"/> and <see cref="Generators.Range"/>
|
||||
/// Unlike the generators available in the <see cref="Generators"/> class,
|
||||
/// this operator relies on an OS timer. This guarantees that messages are emitted at regular wall-clock intervals regardless of pipeline load.
|
||||
/// When the pipeline is in replay mode, the originating times of the messages are derived from the virtual pipeline time,
|
||||
/// but if the pipeline slows down, the interval between messages might not appear constant.
|
||||
|
@ -31,7 +30,7 @@ namespace Microsoft.Psi
|
|||
|
||||
/// <summary>
|
||||
/// Generates a stream of <see cref="TimeSpan"/> messages indicating the time elapsed from the start of the pipeline.
|
||||
/// Unlike <see cref="Generators.Sequence{T}(Pipeline, IEnumerable{T}, TimeSpan, DateTime?)"/>, <see cref="Generators.Repeat{T}(Pipeline, T, int, TimeSpan, DateTime?)"/> and <see cref="Generators.Range"/>
|
||||
/// Unlike the generators available in the <see cref="Generators"/> class,
|
||||
/// this operator relies on an OS timer. This guarantees that messages are emitted at regular wall-clock intervals regardless of pipeline load.
|
||||
/// When the pipeline is in replay mode, the originating times of the messages are derived from the virtual pipeline time,
|
||||
/// but if the pipeline slows down, the interval between messages might not appear constant.
|
||||
|
@ -41,7 +40,7 @@ namespace Microsoft.Psi
|
|||
/// <returns>A stream of messages representing time elapsed since the start of the pipeline.</returns>
|
||||
public static IProducer<TimeSpan> Timer(Pipeline pipeline, TimeSpan interval)
|
||||
{
|
||||
return Timer<TimeSpan>(pipeline, interval, (_, t) => t);
|
||||
return Timer(pipeline, interval, (_, t) => t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,17 +24,19 @@ namespace Microsoft.Psi
|
|||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="vectorSize">Vector arity.</param>
|
||||
/// <param name="streamTransform">Function mapping from an index and stream of input element to a stream of output element.</param>
|
||||
/// <param name="joinOrDefault">When true, a result is produced even if a message is dropped in processing one of the input elements. In this case the corresponding output element is set to default.</param>
|
||||
/// <param name="outputDefaultIfDropped">When true, a result is produced even if a message is dropped in processing one of the input elements. In this case the corresponding output element is set to a default value.</param>
|
||||
/// <param name="defaultValue">Default value to use when messages are dropped in processing one of the input elements.</param>
|
||||
/// <param name="deliveryPolicy">An optional delivery policy.</param>
|
||||
/// <returns>Stream of output arrays.</returns>
|
||||
public static IProducer<TOut[]> Parallel<TIn, TOut>(
|
||||
this IProducer<TIn[]> source,
|
||||
int vectorSize,
|
||||
Func<int, IProducer<TIn>, IProducer<TOut>> streamTransform,
|
||||
bool joinOrDefault = false,
|
||||
bool outputDefaultIfDropped = false,
|
||||
TOut defaultValue = default,
|
||||
DeliveryPolicy deliveryPolicy = null)
|
||||
{
|
||||
var p = new ParallelFixedLength<TIn, TOut>(source.Out.Pipeline, vectorSize, streamTransform, joinOrDefault);
|
||||
var p = new ParallelFixedLength<TIn, TOut>(source.Out.Pipeline, vectorSize, streamTransform, outputDefaultIfDropped, defaultValue);
|
||||
return PipeTo(source, p, deliveryPolicy);
|
||||
}
|
||||
|
||||
|
@ -69,17 +71,19 @@ namespace Microsoft.Psi
|
|||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="vectorSize">Vector arity.</param>
|
||||
/// <param name="streamTransform">Function mapping from an input element stream to an output element stream.</param>
|
||||
/// <param name="joinOrDefault">When true, a result is produced even if a message is dropped in processing one of the input elements. In this case the corresponding output element is set to default.</param>
|
||||
/// <param name="outputDefaultIfDropped">When true, a result is produced even if a message is dropped in processing one of the input elements. In this case the corresponding output element is set to a default value.</param>
|
||||
/// <param name="defaultValue">Default value to use when messages are dropped in processing one of the input elements.</param>
|
||||
/// <param name="deliveryPolicy">An optional delivery policy.</param>
|
||||
/// <returns>Stream of output arrays.</returns>
|
||||
public static IProducer<TOut[]> Parallel<TIn, TOut>(
|
||||
this IProducer<TIn[]> source,
|
||||
int vectorSize,
|
||||
Func<IProducer<TIn>, IProducer<TOut>> streamTransform,
|
||||
bool joinOrDefault = false,
|
||||
bool outputDefaultIfDropped = false,
|
||||
TOut defaultValue = default,
|
||||
DeliveryPolicy deliveryPolicy = null)
|
||||
{
|
||||
return source.Parallel(vectorSize, (i, s) => streamTransform(s), joinOrDefault, deliveryPolicy);
|
||||
return source.Parallel(vectorSize, (i, s) => streamTransform(s), outputDefaultIfDropped, defaultValue, deliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -114,16 +118,18 @@ namespace Microsoft.Psi
|
|||
/// <typeparam name="TOut">Type of output array element.</typeparam>
|
||||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="streamTransform">Function mapping from an input element stream to an output element stream.</param>
|
||||
/// <param name="joinOrDefault">When true, a result is produced even if a message is dropped in processing one of the input elements. In this case the corresponding output element is set to default.</param>
|
||||
/// <param name="outputDefaultIfDropped">When true, a result is produced even if a message is dropped in processing one of the input elements. In this case the corresponding output element is set to a default value.</param>
|
||||
/// <param name="defaultValue">Default value to use when messages are dropped in processing one of the input elements.</param>
|
||||
/// <param name="deliveryPolicy">An optional delivery policy.</param>
|
||||
/// <returns>Stream of output arrays.</returns>
|
||||
public static IProducer<TOut[]> Parallel<TIn, TOut>(
|
||||
this IProducer<TIn[]> source,
|
||||
Func<int, IProducer<TIn>, IProducer<TOut>> streamTransform,
|
||||
bool joinOrDefault = false,
|
||||
bool outputDefaultIfDropped = false,
|
||||
TOut defaultValue = default,
|
||||
DeliveryPolicy deliveryPolicy = null)
|
||||
{
|
||||
var p = new ParallelVariableLength<TIn, TOut>(source.Out.Pipeline, streamTransform, joinOrDefault);
|
||||
var p = new ParallelVariableLength<TIn, TOut>(source.Out.Pipeline, streamTransform, outputDefaultIfDropped, defaultValue);
|
||||
return PipeTo(source, p, deliveryPolicy);
|
||||
}
|
||||
|
||||
|
@ -155,16 +161,18 @@ namespace Microsoft.Psi
|
|||
/// <typeparam name="TOut">Type of output array element.</typeparam>
|
||||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="streamTransform">Function mapping from an input element stream to an output element stream.</param>
|
||||
/// <param name="joinOrDefault">When true, a result is produced even if a message is dropped in processing one of the input elements. In this case the corresponding output element is set to default.</param>
|
||||
/// <param name="outputDefaultIfDropped">When true, a result is produced even if a message is dropped in processing one of the input elements. In this case the corresponding output element is set to a default value.</param>
|
||||
/// <param name="defaultValue">Default value to use when messages are dropped in processing one of the input elements.</param>
|
||||
/// <param name="deliveryPolicy">An optional delivery policy.</param>
|
||||
/// <returns>Stream of output arrays.</returns>
|
||||
public static IProducer<TOut[]> Parallel<TIn, TOut>(
|
||||
this IProducer<TIn[]> source,
|
||||
Func<IProducer<TIn>, IProducer<TOut>> streamTransform,
|
||||
bool joinOrDefault = false,
|
||||
bool outputDefaultIfDropped = false,
|
||||
TOut defaultValue = default,
|
||||
DeliveryPolicy deliveryPolicy = null)
|
||||
{
|
||||
return source.Parallel((i, s) => streamTransform(s), joinOrDefault, deliveryPolicy);
|
||||
return source.Parallel((i, s) => streamTransform(s), outputDefaultIfDropped, defaultValue, deliveryPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -198,18 +206,20 @@ namespace Microsoft.Psi
|
|||
/// <typeparam name="TOut">Type of output dictionary values.</typeparam>
|
||||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="streamTransform">Function mapping from an input element stream to an output element stream.</param>
|
||||
/// <param name="joinOrDefault">When true, a result is produced even if a message is dropped in processing one of the input elements. In this case the corresponding output element is set to default.</param>
|
||||
/// <param name="outputDefaultIfDropped">When true, a result is produced even if a message is dropped in processing one of the input elements. In this case the corresponding output element is set to a default value.</param>
|
||||
/// <param name="defaultValue">Default value to use when messages are dropped in processing one of the input elements.</param>
|
||||
/// <param name="deliveryPolicy">An optional delivery policy.</param>
|
||||
/// <param name="branchTerminationPolicy">Predicate function determining whether and when (originating time) to terminate branches (defaults to when key no longer present), given the current key, message payload (dictionary) and originating time.</param>
|
||||
/// <returns>Stream of output dictionaries.</returns>
|
||||
public static IProducer<Dictionary<TKey, TOut>> Parallel<TIn, TKey, TOut>(
|
||||
this IProducer<Dictionary<TKey, TIn>> source,
|
||||
Func<TKey, IProducer<TIn>, IProducer<TOut>> streamTransform,
|
||||
bool joinOrDefault = false,
|
||||
bool outputDefaultIfDropped = false,
|
||||
TOut defaultValue = default,
|
||||
DeliveryPolicy deliveryPolicy = null,
|
||||
Func<TKey, Dictionary<TKey, TIn>, DateTime, (bool, DateTime)> branchTerminationPolicy = null)
|
||||
{
|
||||
var p = new ParallelSparse<TIn, TKey, TOut>(source.Out.Pipeline, streamTransform, joinOrDefault, branchTerminationPolicy);
|
||||
var p = new ParallelSparse<TIn, TKey, TOut>(source.Out.Pipeline, streamTransform, outputDefaultIfDropped, defaultValue, branchTerminationPolicy);
|
||||
return PipeTo(source, p, deliveryPolicy);
|
||||
}
|
||||
|
||||
|
@ -245,18 +255,20 @@ namespace Microsoft.Psi
|
|||
/// <typeparam name="TOut">Type of output dictionary values.</typeparam>
|
||||
/// <param name="source">Source stream.</param>
|
||||
/// <param name="streamTransform">Function mapping from an input element stream to an output output element stream.</param>
|
||||
/// <param name="joinOrDefault">When true, a result is produced even if a message is dropped in processing one of the input elements. In this case the corresponding output element is set to default.</param>
|
||||
/// <param name="outputDefaultIfDropped">When true, a result is produced even if a message is dropped in processing one of the input elements. In this case the corresponding output element is set to default.</param>
|
||||
/// <param name="defaultValue">Default value to use when messages are dropped in processing one of the input elements.</param>
|
||||
/// <param name="deliveryPolicy">An optional delivery policy.</param>
|
||||
/// <param name="branchTerminationPolicy">Predicate function determining whether and when (originating time) to terminate branches (defaults to when key no longer present), given the current key, dictionary of values and the originating time of the last message containing the key.</param>
|
||||
/// <returns>Stream of output dictionaries.</returns>
|
||||
public static IProducer<Dictionary<TKey, TOut>> Parallel<TIn, TKey, TOut>(
|
||||
this IProducer<Dictionary<TKey, TIn>> source,
|
||||
Func<IProducer<TIn>, IProducer<TOut>> streamTransform,
|
||||
bool joinOrDefault = false,
|
||||
bool outputDefaultIfDropped = false,
|
||||
TOut defaultValue = default,
|
||||
DeliveryPolicy deliveryPolicy = null,
|
||||
Func<TKey, Dictionary<TKey, TIn>, DateTime, (bool, DateTime)> branchTerminationPolicy = null)
|
||||
{
|
||||
return source.Parallel((k, s) => streamTransform(s), joinOrDefault, deliveryPolicy, branchTerminationPolicy);
|
||||
return source.Parallel((k, s) => streamTransform(s), outputDefaultIfDropped, defaultValue, deliveryPolicy, branchTerminationPolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -85,6 +85,13 @@ namespace Microsoft.Psi.Streams
|
|||
|
||||
internal int Count => this.queue.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Try to dequeue the oldest message that obeys the defined delivery policy.
|
||||
/// </summary>
|
||||
/// <param name="message">The oldest message if it exists, or default otherwise.</param>
|
||||
/// <param name="stateTransition">Struct that describes the status of the internal queue after the dequeue.</param>
|
||||
/// <param name="currentTime">The current time of the pipeline that is used to calculate latency.</param>
|
||||
/// <returns>True if oldest message that satisfies the policy is found.</returns>
|
||||
public bool TryDequeue(out Message<T> message, out QueueTransition stateTransition, DateTime currentTime)
|
||||
{
|
||||
message = default(Message<T>);
|
||||
|
@ -92,6 +99,7 @@ namespace Microsoft.Psi.Streams
|
|||
|
||||
lock (this.queue)
|
||||
{
|
||||
// loop through the queue
|
||||
while (this.queue.Count != 0)
|
||||
{
|
||||
var oldest = this.queue.Dequeue();
|
||||
|
@ -99,6 +107,9 @@ namespace Microsoft.Psi.Streams
|
|||
// check if we have a maximum-latency policy and a message that exceeds that latency
|
||||
if (this.policy.MaximumLatency.HasValue && (currentTime - oldest.OriginatingTime) > this.policy.MaximumLatency.Value)
|
||||
{
|
||||
// Because this message has a latency that is larger than the policy allowed, we are dropping this message and
|
||||
// finding the next message with smaller latency.
|
||||
this.pipeline.DiagnosticsCollector?.MessageDropped(this.pipeline, this.element, this.receiver, this.Count, message.Envelope);
|
||||
this.cloner.Recycle(oldest.Data);
|
||||
this.counters?.Increment(ReceiverCounters.Dropped);
|
||||
}
|
||||
|
@ -117,14 +128,20 @@ namespace Microsoft.Psi.Streams
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueue the message while respecting the defined delivery policy.
|
||||
/// </summary>
|
||||
/// <param name="message">The new message.</param>
|
||||
/// <param name="stateTransition">The struct describing the status of the internal queue.</param>
|
||||
public void Enqueue(Message<T> message, out QueueTransition stateTransition)
|
||||
{
|
||||
lock (this.queue)
|
||||
{
|
||||
if (this.queue.Count > this.policy.MaximumQueueSize)
|
||||
// If the queue size is more than the allowed size in the policy, recycle the oldest object in the queue.
|
||||
if (this.queue.Count >= this.policy.MaximumQueueSize)
|
||||
{
|
||||
var item = this.queue.Dequeue(); // discard unprocessed items if the policy requires it
|
||||
this.pipeline.DiagnosticsCollector?.MessageDropped(this.pipeline, this.element, this.receiver, this.Count, message.Envelope);
|
||||
this.pipeline.DiagnosticsCollector?.MessageDropped(this.pipeline, this.element, this.receiver, this.Count, item.Envelope);
|
||||
this.cloner.Recycle(item.Data);
|
||||
this.counters?.Increment(ReceiverCounters.Dropped);
|
||||
}
|
||||
|
@ -136,12 +153,16 @@ namespace Microsoft.Psi.Streams
|
|||
while (this.queue.Count > 0 && this.queue.Peek().OriginatingTime > message.OriginatingTime)
|
||||
{
|
||||
var item = this.queue.Dequeue(); // discard unprocessed items which occur after the closing message
|
||||
this.pipeline.DiagnosticsCollector?.MessageDropped(this.pipeline, this.element, this.receiver, this.Count, item.Envelope);
|
||||
this.cloner.Recycle(item.Data);
|
||||
this.counters?.Increment(ReceiverCounters.Dropped);
|
||||
}
|
||||
}
|
||||
|
||||
// enqueue the new message
|
||||
this.queue.Enqueue(message);
|
||||
|
||||
// Update a bunch of variables that helps with diagnostics and performance measurement.
|
||||
if (this.queue.Count > this.maxQueueSize)
|
||||
{
|
||||
this.maxQueueSize = this.queue.Count;
|
||||
|
@ -153,6 +174,7 @@ namespace Microsoft.Psi.Streams
|
|||
this.counters.RawValue(ReceiverCounters.MaxQueueSize, this.maxQueueSize);
|
||||
}
|
||||
|
||||
// computes the new state that indicates the status of the internal queue and whether the queue needs to be locked and expanded.
|
||||
stateTransition = this.UpdateState();
|
||||
this.pipeline.DiagnosticsCollector?.MessageEnqueued(this.pipeline, this.element, this.receiver, this.Count, message.Envelope);
|
||||
}
|
||||
|
@ -163,16 +185,25 @@ namespace Microsoft.Psi.Streams
|
|||
this.counters = counters;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the status of the <see cref="DeliveryQueue{T}"/> object by comparing different properties of the object (before update) with the
|
||||
/// status of the internal Queue object.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="QueueTransition"/> struct that describe the current state of the <see cref="DeliveryQueue{T}"/>.</returns>
|
||||
private QueueTransition UpdateState()
|
||||
{
|
||||
// save the previous state information locally.
|
||||
int count = this.queue.Count;
|
||||
bool wasEmpty = this.isEmpty;
|
||||
bool wasThrottling = this.isThrottling;
|
||||
bool wasClosing = this.nextMessageEnvelope.SequenceId == int.MaxValue;
|
||||
|
||||
// update the local variables.
|
||||
this.isEmpty = count == 0;
|
||||
this.isThrottling = this.policy.ThrottleWhenFull && count > this.policy.MaximumQueueSize;
|
||||
this.nextMessageEnvelope = (count == 0) ? default(Envelope) : this.queue.Peek().Envelope;
|
||||
|
||||
// create the Transition object by comparing the current and previous local state variables.
|
||||
return new QueueTransition()
|
||||
{
|
||||
ToEmpty = !wasEmpty && this.isEmpty,
|
||||
|
|
|
@ -210,9 +210,14 @@ namespace Microsoft.Psi
|
|||
if (this.lastEnvelope.SequenceId != 0 && msg.SequenceId != int.MaxValue)
|
||||
{
|
||||
// make sure the data is consistent
|
||||
if (msg.Envelope.OriginatingTime <= this.lastEnvelope.OriginatingTime || msg.Envelope.Time < this.lastEnvelope.Time || msg.Envelope.SequenceId <= this.lastEnvelope.SequenceId)
|
||||
if (msg.Envelope.SequenceId <= this.lastEnvelope.SequenceId)
|
||||
{
|
||||
throw new InvalidOperationException("Attempted to post a message without strictly increasing originating time or that is out of order in wall-clock time or sequence ID: " + this.Name);
|
||||
throw new InvalidOperationException($"Attempted to post a message with a sequence ID that is out of order: {this.Name}\nThis may be caused by simultaneous calls to Emitter.Post() from multiple threads.");
|
||||
}
|
||||
|
||||
if (msg.Envelope.OriginatingTime <= this.lastEnvelope.OriginatingTime || msg.Envelope.Time < this.lastEnvelope.Time)
|
||||
{
|
||||
throw new InvalidOperationException($"Attempted to post a message without strictly increasing originating time or that is out of order in wall-clock time: {this.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -231,7 +231,13 @@ namespace Microsoft.Psi
|
|||
bool delivered = this.scheduler.TryExecute(this.syncContext, this.onReceived, message, message.OriginatingTime, this.schedulerContext);
|
||||
if (delivered)
|
||||
{
|
||||
this.pipeline.DiagnosticsCollector?.MessageProcessedSynchronously(this.pipeline, this.element, this, this.awaitingDelivery.Count, message.Envelope, this.ComputeDataSize(message.Data));
|
||||
this.pipeline.DiagnosticsCollector?.MessageProcessedSynchronously(
|
||||
this.pipeline,
|
||||
this.element,
|
||||
this,
|
||||
this.awaitingDelivery.Count,
|
||||
message.Envelope,
|
||||
this.pipeline.DiagnosticsConfiguration.TrackMessageSize ? this.ComputeDataSize(message.Data) : 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -285,7 +291,13 @@ namespace Microsoft.Psi
|
|||
}
|
||||
|
||||
DateTime start = (this.counters != null) ? Time.GetCurrentTime() : default(DateTime);
|
||||
this.pipeline.DiagnosticsCollector?.MessageProcessStart(this.pipeline, this.element, this, this.awaitingDelivery.Count, message.Envelope, this.ComputeDataSize(message.Data));
|
||||
this.pipeline.DiagnosticsCollector?.MessageProcessStart(
|
||||
this.pipeline,
|
||||
this.element,
|
||||
this,
|
||||
this.awaitingDelivery.Count,
|
||||
message.Envelope,
|
||||
this.pipeline.DiagnosticsConfiguration.TrackMessageSize ? this.ComputeDataSize(message.Data) : 0);
|
||||
this.onReceived(message);
|
||||
this.pipeline.DiagnosticsCollector?.MessageProcessComplete(this.pipeline, this.element, this, message.Envelope);
|
||||
|
||||
|
|
|
@ -10,6 +10,6 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation. All rights reserved.")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("be194924-7162-405d-bf6e-e6086baa12f1")]
|
||||
[assembly: AssemblyVersion("0.9.6.1")]
|
||||
[assembly: AssemblyFileVersion("0.9.6.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.9.6.1-beta")]
|
||||
[assembly: AssemblyVersion("0.10.16.1")]
|
||||
[assembly: AssemblyFileVersion("0.10.16.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.10.16.1-beta")]
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace Test.Psi
|
|||
{
|
||||
using (var p = Pipeline.Create("SimplePipeline"))
|
||||
{
|
||||
var generate = Generators.Sequence(p, new[] { 0d, 1d }, x => new[] { x[0] + 0.1, x[1] + 0.1 }, 10);
|
||||
var generate = Generators.Sequence(p, new[] { 0d, 1d }, x => new[] { x[0] + 0.1, x[1] + 0.1 }, 10, TimeSpan.FromTicks(1));
|
||||
var transform = new ScalarMultiplier(p, 100);
|
||||
generate.PipeTo(transform);
|
||||
transform.Do(a => Console.WriteLine($"[{a[0]}, {a[1]}]"));
|
||||
|
@ -38,7 +38,7 @@ namespace Test.Psi
|
|||
using (var p = Pipeline.Create("JoinPipeline"))
|
||||
{
|
||||
// create a generator that will produce a finite sequence
|
||||
var generator = Generators.Sequence(p, new[] { 0d, 1d }, x => new[] { x[0] + 0.1, x[1] + 0.1 }, count: 10);
|
||||
var generator = Generators.Sequence(p, new[] { 0d, 1d }, x => new[] { x[0] + 0.1, x[1] + 0.1 }, 10, TimeSpan.FromTicks(1));
|
||||
|
||||
// instantiate our sample component
|
||||
var multiply = new ScalarMultiplier(p, 10);
|
||||
|
@ -92,7 +92,7 @@ namespace Test.Psi
|
|||
{
|
||||
using (var p = Pipeline.Create(nameof(this.ExceptionHandling)))
|
||||
{
|
||||
var source = Generators.Sequence(p, 1, x => x + 1, 100);
|
||||
var source = Generators.Sequence(p, 1, x => x + 1, 100, TimeSpan.FromTicks(1));
|
||||
var sin = source.Select(t => t / 0); // trigger an exception
|
||||
try
|
||||
{
|
||||
|
@ -113,7 +113,7 @@ namespace Test.Psi
|
|||
{
|
||||
using (var p = Pipeline.Create("pipeline"))
|
||||
{
|
||||
var source = Generators.Sequence(p, 1, x => x + 1, 100);
|
||||
var source = Generators.Sequence(p, 1, x => x + 1, 100, TimeSpan.FromTicks(1));
|
||||
p.PipelineRun += (s, e) =>
|
||||
{
|
||||
int x = 0;
|
||||
|
@ -132,7 +132,7 @@ namespace Test.Psi
|
|||
using (var p = Pipeline.Create("pipeline"))
|
||||
{
|
||||
var sub = new Subpipeline(p, "subpipeline");
|
||||
var generator = Generators.Sequence(p, 1, x => x + 1, 100);
|
||||
var generator = Generators.Sequence(p, 1, x => x + 1, 100, TimeSpan.FromTicks(1));
|
||||
|
||||
sub.PipelineRun += (s, e) =>
|
||||
{
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
namespace Test.Psi
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.Psi;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
@ -17,7 +18,7 @@ namespace Test.Psi
|
|||
DebugExtensions.EnableDebugViews();
|
||||
using (var p = Pipeline.Create())
|
||||
{
|
||||
var name = Generators.Sequence(p, new[] { 1, 2, 3 }).DebugView();
|
||||
var name = Generators.Sequence(p, new[] { 1, 2, 3 }, TimeSpan.FromTicks(1)).DebugView();
|
||||
Assert.IsNotNull(name);
|
||||
}
|
||||
|
||||
|
|
|
@ -36,5 +36,29 @@ namespace Test.Psi
|
|||
Assert.AreNotEqual(0, countA);
|
||||
}
|
||||
}
|
||||
|
||||
// Test to make sure the DeliveryQueue returns the latest item when using a LatestMessage policy.
|
||||
[TestMethod]
|
||||
[Timeout(2000)]
|
||||
public void LatestDelivery()
|
||||
{
|
||||
using (var p = Pipeline.Create())
|
||||
{
|
||||
int loopCount = 0;
|
||||
int lastMsg = 0;
|
||||
var numGen = Generators.Range(p, 10, 3, TimeSpan.FromMilliseconds(100));
|
||||
numGen.Do(m =>
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
loopCount++;
|
||||
lastMsg = m;
|
||||
}, DeliveryPolicy.LatestMessage);
|
||||
|
||||
p.Run(TimeSpan.FromMilliseconds(700));
|
||||
|
||||
Assert.AreEqual(2, loopCount);
|
||||
Assert.AreEqual(12, lastMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
namespace Test.Psi
|
||||
{
|
||||
using Microsoft.Psi;
|
||||
using Microsoft.Psi.Components;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Runs a series of tests for stream generators.
|
||||
|
@ -13,6 +15,72 @@ namespace Test.Psi
|
|||
[TestClass]
|
||||
public class GeneratorsTests
|
||||
{
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void FiniteAndInfiniteGeneratorsTest()
|
||||
{
|
||||
// pipeline containing Generators.Return should stop once post happens
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
Generators.Return(pipeline, 123);
|
||||
pipeline.RunAsync();
|
||||
var stopped = pipeline.WaitAll(2000);
|
||||
Assert.IsTrue(stopped);
|
||||
}
|
||||
|
||||
// pipeline containing Generators.Once should not stop once post happens
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
Generators.Once(pipeline, 123);
|
||||
pipeline.RunAsync();
|
||||
var stopped = pipeline.WaitAll(2000);
|
||||
Assert.IsFalse(stopped);
|
||||
}
|
||||
|
||||
// pipeline containing Generators.Return and Generators.Once should stop, b/c Return stops.
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
Generators.Once(pipeline, 123);
|
||||
Generators.Return(pipeline, 123);
|
||||
pipeline.RunAsync();
|
||||
var stopped = pipeline.WaitAll(2000);
|
||||
Assert.IsTrue(stopped);
|
||||
}
|
||||
|
||||
// pipeline containing an infinite Generators.Repeat should not stop
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
Generators.Repeat(pipeline, 123, TimeSpan.FromMilliseconds(1));
|
||||
pipeline.RunAsync();
|
||||
var stopped = pipeline.WaitAll(2000);
|
||||
Assert.IsFalse(stopped);
|
||||
}
|
||||
|
||||
// pipeline containing a finite Generators.Repeat should stop
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
Generators.Repeat(pipeline, 123, 10, TimeSpan.FromMilliseconds(1));
|
||||
pipeline.RunAsync();
|
||||
var stopped = pipeline.WaitAll(2000);
|
||||
Assert.IsTrue(stopped);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void GeneratorSequenceCountTest()
|
||||
{
|
||||
var list = new List<int>();
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
Generators.Repeat(pipeline, 0, 5, TimeSpan.FromMilliseconds(1)).Do(x => list.Add(x));
|
||||
pipeline.Run();
|
||||
}
|
||||
|
||||
CollectionAssert.AreEqual(list, new int[] { 0, 0, 0, 0, 0 });
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void AlignedSequenceTest()
|
||||
|
|
|
@ -37,16 +37,19 @@ namespace Test.Psi
|
|||
|
||||
private DateTime originatingTime;
|
||||
|
||||
private void AssertStringSerialization(dynamic value, string expected, IFormatSerializer serializer, IFormatDeserializer deserializer)
|
||||
private void AssertStringSerialization(dynamic value, string expected, IFormatSerializer serializer, IFormatDeserializer deserializer, bool roundTrip = true)
|
||||
{
|
||||
var serialized = serializer.SerializeMessage(value, originatingTime);
|
||||
Assert.AreEqual<string>(expected, Encoding.UTF8.GetString(serialized.Item1, serialized.Item2, serialized.Item3));
|
||||
|
||||
var deserialized = deserializer.DeserializeMessage(serialized.Item1, serialized.Item2, serialized.Item3);
|
||||
Assert.AreEqual(originatingTime, deserialized.Item2);
|
||||
if (roundTrip)
|
||||
{
|
||||
var deserialized = deserializer.DeserializeMessage(serialized.Item1, serialized.Item2, serialized.Item3);
|
||||
Assert.AreEqual(originatingTime, deserialized.Item2);
|
||||
|
||||
var roundtrip = serializer.SerializeMessage(deserialized.Item1, originatingTime);
|
||||
Assert.AreEqual<string>(expected, Encoding.UTF8.GetString(roundtrip.Item1, roundtrip.Item2, roundtrip.Item3));
|
||||
var roundtrip = serializer.SerializeMessage(deserialized.Item1, originatingTime);
|
||||
Assert.AreEqual<string>(expected, Encoding.UTF8.GetString(roundtrip.Item1, roundtrip.Item2, roundtrip.Item3));
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertBinarySerialization(dynamic value, IFormatSerializer serializer, IFormatDeserializer deserializer)
|
||||
|
@ -123,12 +126,30 @@ namespace Test.Psi
|
|||
X = 213,
|
||||
Y = 107,
|
||||
Width = 42,
|
||||
Height = 61
|
||||
Height = 61,
|
||||
Points = new [] { 123, 456 }
|
||||
}
|
||||
};
|
||||
// notice Face is traversed but flattened - no hierarchy allowed
|
||||
AssertStringSerialization(structured, "_OriginatingTime_,ID,Confidence,X,Y,Width,Height\r\n1971-11-03T00:00:00.1234567Z,123,0.92,213,107,42,61\r\n", csv, csv);
|
||||
|
||||
var structuredAmbiguous = new
|
||||
{
|
||||
ID = 123,
|
||||
Confidence = 0.92,
|
||||
Face = new
|
||||
{
|
||||
Confidence = 0.89,
|
||||
X = 213,
|
||||
Y = 107,
|
||||
Width = 42,
|
||||
Height = 61
|
||||
}
|
||||
};
|
||||
|
||||
// notice Face is traversed but flattened - no hierarchy allowed
|
||||
AssertStringSerialization(structuredAmbiguous, "_OriginatingTime_,ID,Confidence,Confidence,X,Y,Width,Height\r\n1971-11-03T00:00:00.1234567Z,123,0.92,0.89,213,107,42,61\r\n", csv, csv, false);
|
||||
|
||||
var flat = new
|
||||
{
|
||||
ID = 123,
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Test.Psi
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Psi;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
[TestClass]
|
||||
public class InterpolateTest
|
||||
{
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void LinearInterpolate()
|
||||
{
|
||||
var evenres = new List<(double, DateTime)>();
|
||||
var oddres = new List<(double, DateTime)>();
|
||||
var results = new List<double>();
|
||||
|
||||
using (var p = Pipeline.Create())
|
||||
{
|
||||
var source = Generators.Sequence(p, 0, i => i + 1, 10, TimeSpan.FromTicks(50));
|
||||
var even = source.Where(i => i % 2 == 0).Select(v => (double)v).Do((m, e) => evenres.Add((m, e.OriginatingTime)));
|
||||
var odd = source.Where(i => i % 2 == 1).Select(v => (double)v).Do((m, e) => oddres.Add((m, e.OriginatingTime)));
|
||||
even
|
||||
.Interpolate(odd, Reproducible.Linear())
|
||||
.Do(results.Add);
|
||||
|
||||
p.Run();
|
||||
}
|
||||
|
||||
Assert.AreEqual(results.Count, 4);
|
||||
Assert.AreEqual(results[0], 1, 0.00000001);
|
||||
Assert.AreEqual(results[1], 3, 0.00000001);
|
||||
Assert.AreEqual(results[2], 5, 0.00000001);
|
||||
Assert.AreEqual(results[3], 7, 0.00000001);
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -41,8 +41,8 @@ namespace Test.Psi
|
|||
{
|
||||
using (var p = Pipeline.Create())
|
||||
{
|
||||
// Setup a sequence with a parallel operator, with joinOrDefault = true, and ensure that
|
||||
// the "orDefault" is correctly applied while the instance substream exists, but not outside of that existance.
|
||||
// Setup a sequence with a parallel operator, with outputDefaultIfDropped = true, and ensure that
|
||||
// the "outputDefaultIfDropped" is correctly applied while the instance substream exists, but not outside of that existance.
|
||||
// This tests for making sure we are correctly tracking stream closings and the interpolator
|
||||
// in Join is doing the right thing based on the stream closing times.
|
||||
|
||||
|
@ -62,10 +62,10 @@ namespace Test.Psi
|
|||
new Dictionary<int, int>(),
|
||||
new Dictionary<int, int>(),
|
||||
new Dictionary<int, int>(),
|
||||
});
|
||||
}, TimeSpan.FromTicks(1));
|
||||
|
||||
var resultsParallelOrDefault = new List<int>();
|
||||
input.Parallel(s => s.Where(x => x != 3 && x <= 4), joinOrDefault: true).Do(d =>
|
||||
input.Parallel(s => s.Where(x => x != 3 && x <= 4), outputDefaultIfDropped: true).Do(d =>
|
||||
{
|
||||
if (d.Count() > 0)
|
||||
{
|
||||
|
@ -169,7 +169,7 @@ namespace Test.Psi
|
|||
|
||||
using (var p = Pipeline.Create())
|
||||
{
|
||||
var source = Generators.Sequence(p, 0, i => i + 1, 10);
|
||||
var source = Generators.Sequence(p, 0, i => i + 1, 10, TimeSpan.FromTicks(1));
|
||||
Operators
|
||||
.Join(new[] { source }, TimeSpan.FromTicks(5))
|
||||
.Do(t => results.Add(t.DeepClone()));
|
||||
|
@ -197,7 +197,7 @@ namespace Test.Psi
|
|||
var keyMapping = sourceA.Select(i => (i % 10 != 0) ? new Dictionary<string, int> { { "zero", 0 }, { "one", 1 } } : new Dictionary<string, int> { { "zero", 0 }, { "two", 2 } });
|
||||
|
||||
Operators
|
||||
.Join(keyMapping, new[] { sourceA, sourceB, sourceC }, TimeSpan.FromTicks(5))
|
||||
.Join(keyMapping, new[] { sourceA, sourceB, sourceC }, Reproducible.Nearest<int>(TimeSpan.FromTicks(5)))
|
||||
.Do(t => results.Add(t.DeepClone()));
|
||||
p.Run(new ReplayDescriptor(DateTime.Now, DateTime.MaxValue));
|
||||
}
|
||||
|
@ -234,12 +234,12 @@ namespace Test.Psi
|
|||
|
||||
var tuples =
|
||||
sourceA
|
||||
.Join(sourceB, Match.Best<string>())
|
||||
.Join(sourceC, Match.Best<string>())
|
||||
.Join(sourceD, Match.Best<string>())
|
||||
.Join(sourceE, Match.Best<string>())
|
||||
.Join(sourceF, Match.Best<string>())
|
||||
.Join(sourceG, Match.Best<string>())
|
||||
.Join(sourceB, Reproducible.Nearest<string>())
|
||||
.Join(sourceC, Reproducible.Nearest<string>())
|
||||
.Join(sourceD, Reproducible.Nearest<string>())
|
||||
.Join(sourceE, Reproducible.Nearest<string>())
|
||||
.Join(sourceF, Reproducible.Nearest<string>())
|
||||
.Join(sourceG, Reproducible.Nearest<string>())
|
||||
.ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace Test.Psi
|
|||
double avg = 0;
|
||||
int count = 0;
|
||||
Generators
|
||||
.Sequence(p, new[] { 100d, 50, 0 })
|
||||
.Sequence(p, new[] { 100d, 50, 0 }, TimeSpan.FromTicks(1))
|
||||
.Select(v => avg = avg + (v - avg) / (++count))
|
||||
.Do(Console.WriteLine)
|
||||
.Do(results.Add);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
namespace Test.Psi
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
|
@ -134,7 +135,7 @@ namespace Test.Psi
|
|||
// test simple single subscriber
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var range = Generators.Range(pipeline, 0, 7);
|
||||
var range = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1));
|
||||
results0 = range.ToObservable().ToListObservable(); // terminating (given `pipeline`)
|
||||
results1 = range.ToObservable().Take(7).ToListObservable(); // non-terminating (hence, `Take(7)`)
|
||||
pipeline.Run();
|
||||
|
@ -146,7 +147,7 @@ namespace Test.Psi
|
|||
// test multiple subscribers
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var range = Generators.Range(pipeline, 0, 7);
|
||||
var range = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1));
|
||||
var obs = range.ToObservable(); // single observable
|
||||
results0 = obs.ToListObservable(); // subscribe once
|
||||
results1 = obs.ToListObservable(); // subscribe twice
|
||||
|
@ -159,7 +160,7 @@ namespace Test.Psi
|
|||
// test unsubscribe
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var range = Generators.Range(pipeline, 0, 7);
|
||||
var range = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1));
|
||||
var obs = range.ToObservable();
|
||||
Assert.IsFalse(((Operators.StreamObservable<int>)obs).HasSubscribers);
|
||||
var sub = obs.Subscribe();
|
||||
|
@ -176,7 +177,7 @@ namespace Test.Psi
|
|||
// test errors
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var range = Generators.Range(pipeline, -5, 7); // note -5 .. +1
|
||||
var range = Generators.Range(pipeline, -5, 7, TimeSpan.FromTicks(1)); // note -5 .. +1
|
||||
results0 = range.ToObservable().Select(x => 10 / x).ToListObservable(); // divide by zero!
|
||||
results1 = range.ToObservable().Select(x => 10 / x).OnErrorResumeNext(Observable.Empty<int>()).ToListObservable(); // terminate without exception
|
||||
pipeline.Run();
|
||||
|
@ -202,7 +203,7 @@ namespace Test.Psi
|
|||
{
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var range = Generators.Range(pipeline, 0, 7);
|
||||
var range = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1));
|
||||
var simpleCount = range.Count().ToObservable().ToListObservable();
|
||||
var conditionalCount = range.Count(x => x % 2 == 0).ToObservable().ToListObservable();
|
||||
var simpleLongCount = range.LongCount().ToObservable().ToListObservable();
|
||||
|
@ -222,62 +223,42 @@ namespace Test.Psi
|
|||
{
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var intRange = Generators.Range(pipeline, 0, 7);
|
||||
var intRange = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1));
|
||||
var intSum = intRange.Sum().ToObservable().ToListObservable(); // sum 0 1 2 3 4 5 6
|
||||
var conditionalIntSum = intRange.Sum(x => x % 2 == 0).ToObservable().ToListObservable(); // sum 0 2 4 6
|
||||
var nullableIntSum = intRange.Select(x => x < 5 ? x : (int?)null).Sum().ToObservable().ToListObservable(); // sum 0 1 2 3 4 null null
|
||||
var conditionalNullableIntSum = intRange.Select(x => x < 4 ? x : (int?)null).Sum(x => x % 2 == 0).ToObservable().ToListObservable(); // sum 0 2 null
|
||||
|
||||
var longRange = Generators.Range(pipeline, 0, 7).Select(x => (long)x);
|
||||
var longRange = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1)).Select(x => (long)x);
|
||||
var longSum = longRange.Sum().ToObservable().ToListObservable(); // sum 0 1 2 3 4 5 6
|
||||
var conditionalLongSum = longRange.Sum(x => x % 2L == 0L).ToObservable().ToListObservable(); // sum 0 2 4 6
|
||||
var nullableLongSum = longRange.Select(x => x < 5L ? x : (long?)null).Sum().ToObservable().ToListObservable(); // sum 0 1 2 3 4 null null
|
||||
var conditionalNullableLongSum = longRange.Select(x => x < 4L ? x : (long?)null).Sum(x => x % 2L == 0L).ToObservable().ToListObservable(); // sum 0 2 null
|
||||
|
||||
var floatRange = Generators.Range(pipeline, 0, 7).Select(x => (float)x);
|
||||
var floatRange = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1)).Select(x => (float)x);
|
||||
var floatSum = floatRange.Sum().ToObservable().ToListObservable(); // sum 0 1 2 3 4 5 6
|
||||
var conditionalFloatSum = floatRange.Sum(x => x % 2L == 0L).ToObservable().ToListObservable(); // sum 0 2 4 6
|
||||
var nullableFloatSum = floatRange.Select(x => x < 5L ? x : (float?)null).Sum().ToObservable().ToListObservable(); // sum 0 1 2 3 4 null null
|
||||
var conditionalNullableFloatSum = floatRange.Select(x => x < 4L ? x : (float?)null).Sum(x => x % 2L == 0L).ToObservable().ToListObservable(); // sum 0 2 null
|
||||
|
||||
var doubleRange = Generators.Range(pipeline, 0, 7).Select(x => (double)x);
|
||||
var doubleRange = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1)).Select(x => (double)x);
|
||||
var doubleSum = doubleRange.Sum().ToObservable().ToListObservable(); // sum 0 1 2 3 4 5 6
|
||||
var conditionalDoubleSum = doubleRange.Sum(x => x % 2L == 0L).ToObservable().ToListObservable(); // sum 0 2 4 6
|
||||
var nullableDoubleSum = doubleRange.Select(x => x < 5L ? x : (double?)null).Sum().ToObservable().ToListObservable(); // sum 0 1 2 3 4 null null
|
||||
var conditionalNullableDoubleSum = doubleRange.Select(x => x < 4L ? x : (double?)null).Sum(x => x % 2L == 0L).ToObservable().ToListObservable(); // sum 0 2 null
|
||||
|
||||
var decimalRange = Generators.Range(pipeline, 0, 7).Select(x => (decimal)x);
|
||||
var decimalRange = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1)).Select(x => (decimal)x);
|
||||
var decimalSum = decimalRange.Sum().ToObservable().ToListObservable(); // sum 0 1 2 3 4 5 6
|
||||
var conditionalDecimalSum = decimalRange.Sum(x => x % 2L == 0L).ToObservable().ToListObservable(); // sum 0 2 4 6
|
||||
var nullableDecimalSum = decimalRange.Select(x => x < 5L ? x : (decimal?)null).Sum().ToObservable().ToListObservable(); // sum 0 1 2 3 4 null null
|
||||
var conditionalNullableDecimalSum = decimalRange.Select(x => x < 4L ? x : (decimal?)null).Sum(x => x % 2L == 0L).ToObservable().ToListObservable(); // sum 0 2 null
|
||||
|
||||
pipeline.Run();
|
||||
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new int[] { 0, 1, 3, 6, 10, 15, 21 }, intSum.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new int[] { 0, 2, 6, 12 }, conditionalIntSum.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new int?[] { 0, 1, 3, 6, 10, 10, 10 }, nullableIntSum.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new int?[] { 0, 2 }, conditionalNullableIntSum.AsEnumerable()));
|
||||
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new long[] { 0, 1, 3, 6, 10, 15, 21 }, longSum.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new long[] { 0, 2, 6, 12 }, conditionalLongSum.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new long?[] { 0, 1, 3, 6, 10, 10, 10 }, nullableLongSum.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new long?[] { 0, 2 }, conditionalNullableLongSum.AsEnumerable()));
|
||||
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new double[] { 0, 1, 3, 6, 10, 15, 21 }, doubleSum.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new double[] { 0, 2, 6, 12 }, conditionalDoubleSum.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new double?[] { 0, 1, 3, 6, 10, 10, 10 }, nullableDoubleSum.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new double?[] { 0, 2 }, conditionalNullableDoubleSum.AsEnumerable()));
|
||||
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new float[] { 0, 1, 3, 6, 10, 15, 21 }, floatSum.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new float[] { 0, 2, 6, 12 }, conditionalFloatSum.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new float?[] { 0, 1, 3, 6, 10, 10, 10 }, nullableFloatSum.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new float?[] { 0, 2 }, conditionalNullableFloatSum.AsEnumerable()));
|
||||
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new decimal[] { 0, 1, 3, 6, 10, 15, 21 }, decimalSum.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new decimal[] { 0, 2, 6, 12 }, conditionalDecimalSum.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new decimal?[] { 0, 1, 3, 6, 10, 10, 10 }, nullableDecimalSum.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new decimal?[] { 0, 2 }, conditionalNullableDecimalSum.AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -287,19 +268,19 @@ namespace Test.Psi
|
|||
{
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var intRange = Generators.Range(pipeline, 0, 7);
|
||||
var intRange = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1));
|
||||
var intAverage = intRange.Average().ToObservable().ToListObservable();
|
||||
|
||||
var longRange = Generators.Range(pipeline, 0, 7).Select(x => (long)x);
|
||||
var longRange = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1)).Select(x => (long)x);
|
||||
var longAverage = longRange.Average().ToObservable().ToListObservable();
|
||||
|
||||
var floatRange = Generators.Range(pipeline, 0, 7).Select(x => (float)x);
|
||||
var floatRange = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1)).Select(x => (float)x);
|
||||
var floatAverage = floatRange.Average().ToObservable().ToListObservable();
|
||||
|
||||
var doubleRange = Generators.Range(pipeline, 0, 7).Select(x => (double)x);
|
||||
var doubleRange = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1)).Select(x => (double)x);
|
||||
var doubleAverage = doubleRange.Average().ToObservable().ToListObservable();
|
||||
|
||||
var decimalRange = Generators.Range(pipeline, 0, 7).Select(x => (decimal)x);
|
||||
var decimalRange = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1)).Select(x => (decimal)x);
|
||||
var decimalAverage = decimalRange.Average().ToObservable().ToListObservable();
|
||||
|
||||
pipeline.Run();
|
||||
|
@ -312,6 +293,38 @@ namespace Test.Psi
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void AverageWithCondition()
|
||||
{
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var intRange = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1));
|
||||
var intAverage = intRange.Average(i => i % 2 == 0).ToObservable().ToListObservable();
|
||||
|
||||
var longRange = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1)).Select(x => (long)x);
|
||||
var longAverage = longRange.Average(i => i % 2 == 0).ToObservable().ToListObservable();
|
||||
|
||||
var floatRange = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1)).Select(x => (float)x);
|
||||
var floatAverage = floatRange.Average(i => i % 2 == 0).ToObservable().ToListObservable();
|
||||
|
||||
var doubleRange = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1)).Select(x => (double)x);
|
||||
var doubleAverage = doubleRange.Average(i => i % 2 == 0).ToObservable().ToListObservable();
|
||||
|
||||
var decimalRange = Generators.Range(pipeline, 0, 7, TimeSpan.FromTicks(1)).Select(x => (decimal)x);
|
||||
var decimalAverage = decimalRange.Average(i => i % 2 == 0).ToObservable().ToListObservable();
|
||||
|
||||
pipeline.Run();
|
||||
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new double[] { 0, 1, 2, 3 }, intAverage.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new double[] { 0, 1, 2, 3 }, longAverage.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new float[] { 0, 1, 2, 3 }, floatAverage.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new double[] { 0, 1, 2, 3 }, doubleAverage.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new decimal[] { 0, 1, 2, 3 }, decimalAverage.AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void AverageOverHistory()
|
||||
|
@ -387,6 +400,24 @@ namespace Test.Psi
|
|||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void CountOverHistory()
|
||||
{
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var range = Generators.Range(pipeline, 0, 7, TimeSpan.FromMilliseconds(1));
|
||||
var countHistoryByTime = range.Count(TimeSpan.FromMilliseconds(4)).ToObservable().ToListObservable();
|
||||
var longCountHistoryByTime = range.LongCount(TimeSpan.FromMilliseconds(4)).ToObservable().ToListObservable();
|
||||
|
||||
pipeline.Run();
|
||||
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new int[] { 1, 2, 3, 4, 4, 4, 4 }, countHistoryByTime.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new long[] { 1, 2, 3, 4, 4, 4, 4 }, longCountHistoryByTime.AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void SumOverHistory()
|
||||
|
@ -639,9 +670,9 @@ namespace Test.Psi
|
|||
{
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var factorials = Generators.Range(pipeline, 1, 7).Aggregate((x, y) => x * y).ToObservable().ToListObservable();
|
||||
var single = Generators.Range(pipeline, 1, 1).Aggregate((x, y) => x * y).ToObservable().ToListObservable();
|
||||
var empty = Generators.Range(pipeline, 1, 0).Aggregate((x, y) => x * y).ToObservable().ToListObservable();
|
||||
var factorials = Generators.Range(pipeline, 1, 7, TimeSpan.FromTicks(1)).Aggregate((x, y) => x * y).ToObservable().ToListObservable();
|
||||
var single = Generators.Range(pipeline, 1, 1, TimeSpan.FromTicks(1)).Aggregate((x, y) => x * y).ToObservable().ToListObservable();
|
||||
var empty = Generators.Range(pipeline, 1, 0, TimeSpan.FromTicks(1)).Aggregate((x, y) => x * y).ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new int[] { 1, 2, 6, 24, 120, 720, 5040 }, factorials.AsEnumerable()));
|
||||
|
@ -656,7 +687,7 @@ namespace Test.Psi
|
|||
{
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var seq = Generators.Sequence(pipeline, new int[] { 5, 6, 4, 2, 3, 1, 9 });
|
||||
var seq = Generators.Sequence(pipeline, new int[] { 5, 6, 4, 2, 3, 1, 9 }, TimeSpan.FromTicks(1));
|
||||
var min = seq.Min().ToObservable().ToListObservable();
|
||||
var max = seq.Max().ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
@ -666,14 +697,67 @@ namespace Test.Psi
|
|||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void MinMaxWithComparer()
|
||||
{
|
||||
var reverseComparer = new CompareIntsReversed();
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var seq = Generators.Sequence(pipeline, new int[] { 5, 6, 4, 2, 3, 1, 9 }, TimeSpan.FromTicks(1));
|
||||
var min = seq.Min(reverseComparer).ToObservable().ToListObservable();
|
||||
var max = seq.Max(reverseComparer).ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new int[] { 5, 6, 6, 6, 6, 6, 9 }, min.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new int[] { 5, 5, 4, 2, 2, 1, 1 }, max.AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void MinMaxWithCondition()
|
||||
{
|
||||
Predicate<int> isEvenCondition = i => i % 2 == 0;
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var seq = Generators.Sequence(pipeline, new int[] { 5, 6, 4, 2, 3, 1, 8 }, TimeSpan.FromTicks(1));
|
||||
var min = seq.Min(isEvenCondition).ToObservable().ToListObservable();
|
||||
var max = seq.Max(isEvenCondition).ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new int[] { 6, 4, 2, 2 }, min.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new int[] { 6, 6, 6, 8 }, max.AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void MinMaxWithConditionAndComparer()
|
||||
{
|
||||
Predicate<int> isEvenCondition = i => i % 2 == 0;
|
||||
var reverseComparer = new CompareIntsReversed();
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var seq = Generators.Sequence(pipeline, new int[] { 5, 6, 4, 2, 3, 1, 8 }, TimeSpan.FromTicks(1));
|
||||
var min = seq.Min(isEvenCondition, reverseComparer).ToObservable().ToListObservable();
|
||||
var max = seq.Max(isEvenCondition, reverseComparer).ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new int[] { 6, 6, 6, 8 }, min.AsEnumerable()));
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new int[] { 6, 4, 2, 2 }, max.AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void BufferBySize()
|
||||
{
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var buffers = Generators.Range(pipeline, 0, 5).Window(0, 2).ToObservable().ToListObservable();
|
||||
var timestamps = Generators.Range(pipeline, 0, 5).Select((_, e) => e.OriginatingTime).Window(0, 2).Select((m, e) => Tuple.Create(m.ToArray(), e.OriginatingTime)).ToObservable().ToListObservable();
|
||||
var buffers = Generators.Range(pipeline, 0, 5, TimeSpan.FromTicks(1)).Window(0, 2).ToObservable().ToListObservable();
|
||||
var timestamps = Generators.Range(pipeline, 0, 5, TimeSpan.FromTicks(1)).Select((_, e) => e.OriginatingTime).Window(0, 2).Select((m, e) => Tuple.Create(m.ToArray(), e.OriginatingTime)).ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
var bufferResults = buffers.AsEnumerable().ToArray();
|
||||
|
@ -698,7 +782,7 @@ namespace Test.Psi
|
|||
{
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var sums = Generators.Range(pipeline, 0, 5).Window(0, 2, ms => ms.Select(m => m.Data).Sum()).ToObservable().ToListObservable();
|
||||
var sums = Generators.Range(pipeline, 0, 5, TimeSpan.FromTicks(1)).Window(0, 2, ms => ms.Select(m => m.Data).Sum()).ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
var results = sums.AsEnumerable().ToArray();
|
||||
|
@ -715,8 +799,8 @@ namespace Test.Psi
|
|||
{
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var buffers = Generators.Range(pipeline, 0, 5).Window(-2, 0).ToObservable().ToListObservable();
|
||||
var timestamps = Generators.Range(pipeline, 0, 5).Select((_, e) => e.OriginatingTime).Window(-2, 0).Select((m, e) => Tuple.Create(m.ToArray(), e.OriginatingTime)).ToObservable().ToListObservable();
|
||||
var buffers = Generators.Range(pipeline, 0, 5, TimeSpan.FromTicks(1)).Window(-2, 0).ToObservable().ToListObservable();
|
||||
var timestamps = Generators.Range(pipeline, 0, 5, TimeSpan.FromTicks(1)).Select((_, e) => e.OriginatingTime).Window(-2, 0).Select((m, e) => Tuple.Create(m.ToArray(), e.OriginatingTime)).ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
var bufferResults = buffers.AsEnumerable().ToArray();
|
||||
|
@ -741,7 +825,7 @@ namespace Test.Psi
|
|||
{
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var sums = Generators.Range(pipeline, 0, 5).Window(-2, 0, ms => ms.Select(m => m.Data).Sum()).ToObservable().ToListObservable();
|
||||
var sums = Generators.Range(pipeline, 0, 5, TimeSpan.FromTicks(1)).Window(-2, 0, ms => ms.Select(m => m.Data).Sum()).ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
var results = sums.AsEnumerable().ToArray();
|
||||
|
@ -952,7 +1036,7 @@ namespace Test.Psi
|
|||
{
|
||||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
var windows = Generators.Sequence(pipeline, new IEnumerable<double>[] { new[] { 727.7, 1086.5, 1091.0, 1361.3, 1490.5, 1956.1 }, new double[] { } });
|
||||
var windows = Generators.Sequence(pipeline, new IEnumerable<double>[] { new[] { 727.7, 1086.5, 1091.0, 1361.3, 1490.5, 1956.1 }, new double[] { } }, TimeSpan.FromTicks(1));
|
||||
var std = windows.Std().ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
|
@ -1008,5 +1092,13 @@ namespace Test.Psi
|
|||
return $"{this.GenerateTimeOffsetMs}: {this.Source} -> {this.Data}:{this.OriginatingTimeOffsetMs}";
|
||||
}
|
||||
}
|
||||
|
||||
public class CompareIntsReversed : IComparer<int>
|
||||
{
|
||||
public int Compare(int x, int y)
|
||||
{
|
||||
return x == y ? 0 : x < y ? 1 : -1; // reverse ordering
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,13 +20,17 @@ namespace Test.Psi
|
|||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
Generators.Range(pipeline, 0, 2, TimeSpan.FromSeconds(1)); // hold pipeline open
|
||||
var primary = Generators.Range(pipeline, 0, 5).Delay(TimeSpan.FromMilliseconds(100));
|
||||
var secondary = Generators.Range(pipeline, 0, 5);
|
||||
var primary = Generators.Range(pipeline, 0, 5, TimeSpan.FromTicks(1)).Delay(TimeSpan.FromMilliseconds(100));
|
||||
var secondary = Generators.Range(pipeline, 0, 5, TimeSpan.FromTicks(1));
|
||||
var paired = primary.Pair(secondary).ToObservable().ToListObservable();
|
||||
var fused = primary.Fuse(secondary, Available.Last<int>()).ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
var results = paired.AsEnumerable().ToArray();
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new[] { 0, 1, 2, 3, 4 }.Zip(new[] { 4, 4, 4, 4, 4 }, ValueTuple.Create), results));
|
||||
var pairedResults = paired.AsEnumerable().ToArray();
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new[] { 0, 1, 2, 3, 4 }.Zip(new[] { 4, 4, 4, 4, 4 }, ValueTuple.Create), pairedResults));
|
||||
|
||||
var fusedResults = fused.AsEnumerable().ToArray();
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new[] { 0, 1, 2, 3, 4 }.Zip(new[] { 4, 4, 4, 4, 4 }, ValueTuple.Create), fusedResults));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,13 +41,17 @@ namespace Test.Psi
|
|||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
Generators.Range(pipeline, 0, 2, TimeSpan.FromSeconds(1)); // hold pipeline open
|
||||
var primary = Generators.Range(pipeline, 0, 5);
|
||||
var secondary = Generators.Range(pipeline, 0, 5).Delay(TimeSpan.FromMilliseconds(100));
|
||||
var primary = Generators.Range(pipeline, 0, 5, TimeSpan.FromTicks(1));
|
||||
var secondary = Generators.Range(pipeline, 0, 5, TimeSpan.FromTicks(1)).Delay(TimeSpan.FromMilliseconds(100));
|
||||
var paired = primary.Pair(secondary).ToObservable().ToListObservable();
|
||||
var fused = primary.Fuse(secondary, Available.Last<int>()).ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
var results = paired.AsEnumerable().ToArray();
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new ValueTuple<int, int>[] { }, results));
|
||||
var pairedResults = paired.AsEnumerable().ToArray();
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new ValueTuple<int, int>[] { }, pairedResults));
|
||||
|
||||
var fusedResults = fused.AsEnumerable().ToArray();
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new ValueTuple<int, int>[] { }, fusedResults));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,13 +62,17 @@ namespace Test.Psi
|
|||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
Generators.Range(pipeline, 0, 2, TimeSpan.FromSeconds(1)); // hold pipeline open
|
||||
var primary = Generators.Range(pipeline, 0, 5);
|
||||
var secondary = Generators.Range(pipeline, 0, 5).Delay(TimeSpan.FromMilliseconds(100));
|
||||
var primary = Generators.Range(pipeline, 0, 5, TimeSpan.FromTicks(1));
|
||||
var secondary = Generators.Range(pipeline, 0, 5, TimeSpan.FromTicks(1)).Delay(TimeSpan.FromMilliseconds(100));
|
||||
var paired = primary.Pair(secondary, 42).ToObservable().ToListObservable();
|
||||
var fused = primary.Fuse(secondary, Available.LastOrDefault(42)).ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
var results = paired.AsEnumerable().ToArray();
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new[] { 0, 1, 2, 3, 4 }.Zip(new[] { 42, 42, 42, 42, 42 }, ValueTuple.Create), results));
|
||||
var pairedResults = paired.AsEnumerable().ToArray();
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new[] { 0, 1, 2, 3, 4 }.Zip(new[] { 42, 42, 42, 42, 42 }, ValueTuple.Create), pairedResults));
|
||||
|
||||
var fusedResults = fused.AsEnumerable().ToArray();
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new[] { 0, 1, 2, 3, 4 }.Zip(new[] { 42, 42, 42, 42, 42 }, ValueTuple.Create), pairedResults));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,13 +83,17 @@ namespace Test.Psi
|
|||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
Generators.Range(pipeline, 0, 2, TimeSpan.FromSeconds(1)); // hold pipeline open
|
||||
var primary = Generators.Range(pipeline, 0, 5).Delay(TimeSpan.FromMilliseconds(100));
|
||||
var secondary = Generators.Range(pipeline, 0, 5);
|
||||
var primary = Generators.Range(pipeline, 0, 5, TimeSpan.FromTicks(1)).Delay(TimeSpan.FromMilliseconds(100));
|
||||
var secondary = Generators.Range(pipeline, 0, 5, TimeSpan.FromTicks(1));
|
||||
var paired = primary.Pair(secondary, (p, s) => p * 10 + s).ToObservable().ToListObservable();
|
||||
var fused = primary.Fuse(secondary, Available.Last<int>(), (p, s) => p * 10 + s).ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
var results = paired.AsEnumerable().ToArray();
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new[] { 04, 14, 24, 34, 44 }, results));
|
||||
var pairedResults = paired.AsEnumerable().ToArray();
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new[] { 04, 14, 24, 34, 44 }, pairedResults));
|
||||
|
||||
var fusedResults = fused.AsEnumerable().ToArray();
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new[] { 04, 14, 24, 34, 44 }, fusedResults));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,13 +104,17 @@ namespace Test.Psi
|
|||
using (var pipeline = Pipeline.Create())
|
||||
{
|
||||
Generators.Range(pipeline, 0, 2, TimeSpan.FromSeconds(1)); // hold pipeline open
|
||||
var primary = Generators.Range(pipeline, 0, 5);
|
||||
var secondary = Generators.Range(pipeline, 0, 5).Delay(TimeSpan.FromMilliseconds(100));
|
||||
var primary = Generators.Range(pipeline, 0, 5, TimeSpan.FromTicks(1));
|
||||
var secondary = Generators.Range(pipeline, 0, 5, TimeSpan.FromTicks(1)).Delay(TimeSpan.FromMilliseconds(100));
|
||||
var paired = primary.Pair(secondary, (p, s) => p * 10 + s, 7).ToObservable().ToListObservable();
|
||||
var fused = primary.Fuse(secondary, Available.LastOrDefault(7), (p, s) => p * 10 + s).ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
var results = paired.AsEnumerable().ToArray();
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new[] { 07, 17, 27, 37, 47 }, results));
|
||||
var pairedResults = paired.AsEnumerable().ToArray();
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new[] { 07, 17, 27, 37, 47 }, pairedResults));
|
||||
|
||||
var fusedResults = fused.AsEnumerable().ToArray();
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new[] { 07, 17, 27, 37, 47 }, fusedResults));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,7 +133,7 @@ namespace Test.Psi
|
|||
var sourceF = range.Select(x => $"F{x}");
|
||||
var sourceG = range.Select(x => $"G{x}");
|
||||
|
||||
ListObservable<ValueTuple<string, string, string, string, string, string, string>> tuples = // expecting tuple flattening
|
||||
var pairedTuples = // expecting tuple flattening
|
||||
sourceA
|
||||
.Pair(sourceB, "B?")
|
||||
.Pair(sourceC, "C?")
|
||||
|
@ -122,14 +142,40 @@ namespace Test.Psi
|
|||
.Pair(sourceF, "F?")
|
||||
.Pair(sourceG, "G?")
|
||||
.ToObservable().ToListObservable();
|
||||
|
||||
var fusedTuples = // expecting tuple flattening
|
||||
sourceA
|
||||
.Fuse(sourceB, Available.LastOrDefault("B?"))
|
||||
.Fuse(sourceC, Available.LastOrDefault("C?"))
|
||||
.Fuse(sourceD, Available.LastOrDefault("D?"))
|
||||
.Fuse(sourceE, Available.LastOrDefault("E?"))
|
||||
.Fuse(sourceF, Available.LastOrDefault("F?"))
|
||||
.Fuse(sourceG, Available.LastOrDefault("G?"))
|
||||
.ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
var results = tuples.AsEnumerable().ToArray();
|
||||
var pairedResults = pairedTuples.AsEnumerable().ToArray();
|
||||
|
||||
Assert.AreEqual(10, results.Length);
|
||||
Assert.AreEqual(10, pairedResults.Length);
|
||||
|
||||
// can't really validate content ordering as with Join because Pair is inherently non-deterministic
|
||||
foreach (var r in results)
|
||||
foreach (var r in pairedResults)
|
||||
{
|
||||
Assert.IsTrue(r.Item1.StartsWith("A"));
|
||||
Assert.IsTrue(r.Item2.StartsWith("B"));
|
||||
Assert.IsTrue(r.Item3.StartsWith("C"));
|
||||
Assert.IsTrue(r.Item4.StartsWith("D"));
|
||||
Assert.IsTrue(r.Item5.StartsWith("E"));
|
||||
Assert.IsTrue(r.Item6.StartsWith("F"));
|
||||
Assert.IsTrue(r.Item7.StartsWith("G"));
|
||||
}
|
||||
|
||||
var fusedResults = fusedTuples.AsEnumerable().ToArray();
|
||||
|
||||
Assert.AreEqual(10, fusedResults.Length);
|
||||
|
||||
// can't really validate content ordering as with Join because Fuse is inherently non-deterministic
|
||||
foreach (var r in fusedResults)
|
||||
{
|
||||
Assert.IsTrue(r.Item1.StartsWith("A"));
|
||||
Assert.IsTrue(r.Item2.StartsWith("B"));
|
||||
|
@ -157,7 +203,7 @@ namespace Test.Psi
|
|||
var sourceF = range.Select(x => $"F{x}");
|
||||
var sourceG = range.Select(x => $"G{x}");
|
||||
|
||||
ListObservable<ValueTuple<string, string, string, string, string, string, string>> tuples = // expecting tuple flattening
|
||||
var pairedTuples = // expecting tuple flattening
|
||||
sourceA
|
||||
.Pair(sourceB)
|
||||
.Pair(sourceC)
|
||||
|
@ -166,13 +212,38 @@ namespace Test.Psi
|
|||
.Pair(sourceF)
|
||||
.Pair(sourceG)
|
||||
.ToObservable().ToListObservable();
|
||||
|
||||
var fusedTuples = // expecting tuple flattening
|
||||
sourceA
|
||||
.Fuse(sourceB, Available.Last<string>())
|
||||
.Fuse(sourceC, Available.Last<string>())
|
||||
.Fuse(sourceD, Available.Last<string>())
|
||||
.Fuse(sourceE, Available.Last<string>())
|
||||
.Fuse(sourceF, Available.Last<string>())
|
||||
.Fuse(sourceG, Available.Last<string>())
|
||||
.ToObservable().ToListObservable();
|
||||
pipeline.Run();
|
||||
|
||||
// cannot validate length as above because without initial value, it is non-deterministic
|
||||
var results = tuples.AsEnumerable().ToArray();
|
||||
var pairedResults = pairedTuples.AsEnumerable().ToArray();
|
||||
|
||||
// cannot validate content ordering as with Join because Pair is inherently non-deterministic
|
||||
foreach (var r in results)
|
||||
foreach (var r in pairedResults)
|
||||
{
|
||||
Assert.IsTrue(r.Item1.StartsWith("A"));
|
||||
Assert.IsTrue(r.Item2.StartsWith("B"));
|
||||
Assert.IsTrue(r.Item3.StartsWith("C"));
|
||||
Assert.IsTrue(r.Item4.StartsWith("D"));
|
||||
Assert.IsTrue(r.Item5.StartsWith("E"));
|
||||
Assert.IsTrue(r.Item6.StartsWith("F"));
|
||||
Assert.IsTrue(r.Item7.StartsWith("G"));
|
||||
}
|
||||
|
||||
// cannot validate length as above because without initial value, it is non-deterministic
|
||||
var fusedResults = fusedTuples.AsEnumerable().ToArray();
|
||||
|
||||
// cannot validate content ordering as with Join because Fuse is inherently non-deterministic
|
||||
foreach (var r in fusedResults)
|
||||
{
|
||||
Assert.IsTrue(r.Item1.StartsWith("A"));
|
||||
Assert.IsTrue(r.Item2.StartsWith("B"));
|
||||
|
@ -200,20 +271,44 @@ namespace Test.Psi
|
|||
var sourceF = range.Select(x => $"F{x}");
|
||||
var sourceG = range.Select(x => $"G{x}");
|
||||
|
||||
var tuplesFG = sourceF.Pair(sourceG);
|
||||
var tuplesEFG = sourceE.Pair(tuplesFG);
|
||||
var tuplesDEFG = sourceD.Pair(tuplesEFG);
|
||||
var tuplesCDEFG = sourceC.Pair(tuplesDEFG);
|
||||
var tuplesBCDEFG = sourceB.Pair(tuplesCDEFG);
|
||||
var tuplesABCDEFG = sourceA.Pair(tuplesBCDEFG);
|
||||
ListObservable<ValueTuple<string, string, string, string, string, string, string>> tuples = tuplesABCDEFG.ToObservable().ToListObservable();
|
||||
var pairedTuplesFG = sourceF.Pair(sourceG);
|
||||
var pairedTuplesEFG = sourceE.Pair(pairedTuplesFG);
|
||||
var pairedTuplesDEFG = sourceD.Pair(pairedTuplesEFG);
|
||||
var pairedTuplesCDEFG = sourceC.Pair(pairedTuplesDEFG);
|
||||
var pairedTuplesBCDEFG = sourceB.Pair(pairedTuplesCDEFG);
|
||||
var pairedTuplesABCDEFG = sourceA.Pair(pairedTuplesBCDEFG);
|
||||
var pairedTuples = pairedTuplesABCDEFG.ToObservable().ToListObservable();
|
||||
|
||||
var fusedTuplesFG = sourceF.Fuse(sourceG, Available.Last<string>());
|
||||
var fusedTuplesEFG = sourceE.Fuse(fusedTuplesFG, Available.Last<(string, string)>());
|
||||
var fusedTuplesDEFG = sourceD.Fuse(fusedTuplesEFG, Available.Last<(string, string, string)>());
|
||||
var fusedTuplesCDEFG = sourceC.Fuse(fusedTuplesDEFG, Available.Last<(string, string, string, string)>());
|
||||
var fusedTuplesBCDEFG = sourceB.Fuse(fusedTuplesCDEFG, Available.Last<(string, string, string, string, string)>());
|
||||
var fusedTuplesABCDEFG = sourceA.Fuse(fusedTuplesBCDEFG, Available.Last<(string, string, string, string, string, string)>());
|
||||
var fusedTuples = fusedTuplesABCDEFG.ToObservable().ToListObservable();
|
||||
|
||||
pipeline.Run();
|
||||
|
||||
// cannot validate length as above because without initial value, it is non-deterministic
|
||||
var results = tuples.AsEnumerable().ToArray();
|
||||
var pairedResults = pairedTuples.AsEnumerable().ToArray();
|
||||
|
||||
// can't really validate content ordering as with Join because Pair is inherently non-deterministic
|
||||
foreach (var r in results)
|
||||
foreach (var r in pairedResults)
|
||||
{
|
||||
Assert.IsTrue(r.Item1.StartsWith("A"));
|
||||
Assert.IsTrue(r.Item2.StartsWith("B"));
|
||||
Assert.IsTrue(r.Item3.StartsWith("C"));
|
||||
Assert.IsTrue(r.Item4.StartsWith("D"));
|
||||
Assert.IsTrue(r.Item5.StartsWith("E"));
|
||||
Assert.IsTrue(r.Item6.StartsWith("F"));
|
||||
Assert.IsTrue(r.Item7.StartsWith("G"));
|
||||
}
|
||||
|
||||
// cannot validate length as above because without initial value, it is non-deterministic
|
||||
var fusedResults = fusedTuples.AsEnumerable().ToArray();
|
||||
|
||||
// can't really validate content ordering as with Join because Pair is inherently non-deterministic
|
||||
foreach (var r in fusedResults)
|
||||
{
|
||||
Assert.IsTrue(r.Item1.StartsWith("A"));
|
||||
Assert.IsTrue(r.Item2.StartsWith("B"));
|
||||
|
|
|
@ -127,7 +127,7 @@ namespace Test.Psi
|
|||
using (var p = Pipeline.Create("write"))
|
||||
{
|
||||
var writeStore = Store.Create(p, name, this.path);
|
||||
var seq = Generators.Sequence(p, 1, i => i, 1); // note that this is not written to the store
|
||||
var seq = Generators.Sequence(p, 1, i => i, 1, TimeSpan.FromTicks(1)); // note that this is not written to the store
|
||||
p.Run();
|
||||
}
|
||||
|
||||
|
@ -172,7 +172,7 @@ namespace Test.Psi
|
|||
using (var p = Pipeline.Create("write"))
|
||||
{
|
||||
var writeStore = Store.Create(p, name, this.path);
|
||||
var seq = Generators.Sequence(p, 1, i => i + 1, count);
|
||||
var seq = Generators.Sequence(p, 1, i => i + 1, count, TimeSpan.FromTicks(1));
|
||||
seq.Write("seq", writeStore);
|
||||
p.Run();
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ namespace Test.Psi
|
|||
using (var p = Pipeline.Create("write"))
|
||||
{
|
||||
var writeStore = Store.Create(p, name, this.path);
|
||||
var seq = Generators.Sequence(p, 1, i => i + 1, count);
|
||||
var seq = Generators.Sequence(p, 1, i => i + 1, count, TimeSpan.FromTicks(1));
|
||||
var mul = seq.Select(i => i * factor);
|
||||
var tuple = seq.Select(i => (i, i.ToString()));
|
||||
seq.Write("seq", writeStore);
|
||||
|
@ -328,7 +328,7 @@ namespace Test.Psi
|
|||
using (var p = Pipeline.Create("write"))
|
||||
{
|
||||
var writeStore = Store.Create(p, name, this.path);
|
||||
var seq = Generators.Sequence(p, 1, i => i, count);
|
||||
var seq = Generators.Sequence(p, 1, i => i, count, TimeSpan.FromTicks(1));
|
||||
var big = seq.Select(i => bytes);
|
||||
seq.Write("seq", writeStore);
|
||||
big.Write("big", writeStore, largeMessages: true);
|
||||
|
@ -399,7 +399,7 @@ namespace Test.Psi
|
|||
using (var p = Pipeline.Create("test"))
|
||||
{
|
||||
var writeStore = Store.Create(p, appName, this.path);
|
||||
var source = Generators.Sequence(p, 1, i => i, 100);
|
||||
var source = Generators.Sequence(p, 1, i => i, 100, TimeSpan.FromTicks(1));
|
||||
source.Write(sourceName, writeStore);
|
||||
p.Run();
|
||||
}
|
||||
|
@ -497,7 +497,7 @@ namespace Test.Psi
|
|||
|
||||
var pipelineWrite = Pipeline.Create("write");
|
||||
var writeStore = Store.Create(pipelineWrite, name, relative);
|
||||
var seq = Generators.Sequence(pipelineWrite, 0, i => i + 1, count);
|
||||
var seq = Generators.Sequence(pipelineWrite, 0, i => i + 1, count, TimeSpan.FromTicks(1));
|
||||
seq.Write("seq", writeStore);
|
||||
seq.Do((m, e) => before[m] = e);
|
||||
|
||||
|
@ -544,7 +544,7 @@ namespace Test.Psi
|
|||
using (var p = Pipeline.Create("write"))
|
||||
{
|
||||
var writeStore = Store.Create(p, name, null);
|
||||
var seq = Generators.Sequence(p, 0, i => i + 1, 1);
|
||||
var seq = Generators.Sequence(p, 0, i => i + 1, 1, TimeSpan.FromTicks(1));
|
||||
seq.Write("seq", writeStore);
|
||||
var sel = seq.Select((m, e) => m);
|
||||
p.Run();
|
||||
|
@ -613,7 +613,7 @@ namespace Test.Psi
|
|||
using (var p = Pipeline.Create("write"))
|
||||
{
|
||||
var writeStore = Store.Create(p, name, this.path);
|
||||
var seq = Generators.Sequence(p, 1, i => i + 1, count);
|
||||
var seq = Generators.Sequence(p, 1, i => i + 1, count, TimeSpan.FromTicks(1));
|
||||
seq.Write("seq", writeStore);
|
||||
seq.Select(i => i.ToString()).Write("seqString", writeStore);
|
||||
seq.Do((m, e) => before[m] = e);
|
||||
|
@ -667,7 +667,7 @@ namespace Test.Psi
|
|||
using (var p = Pipeline.Create("write"))
|
||||
{
|
||||
var writeStore = Store.Create(p, name, this.path);
|
||||
var seq = Generators.Sequence(p, 1, i => i + 1, count);
|
||||
var seq = Generators.Sequence(p, 1, i => i + 1, count, TimeSpan.FromTicks(1));
|
||||
seq.Write("seq", writeStore);
|
||||
seq.Select(i => i.ToString()).Write("seqString", writeStore);
|
||||
seq.Do((m, e) => before[m] = e);
|
||||
|
@ -730,7 +730,7 @@ namespace Test.Psi
|
|||
using (var p = Pipeline.Create("write"))
|
||||
{
|
||||
var validStore = Store.Create(p, name, this.path);
|
||||
var seq = Generators.Sequence(p, 1, i => i + 1, count);
|
||||
var seq = Generators.Sequence(p, 1, i => i + 1, count, TimeSpan.FromTicks(1));
|
||||
seq.Write("seq", validStore);
|
||||
seq.Select(i => i.ToString()).Write("seqString", validStore);
|
||||
seq.Do((m, e) => valid[m] = e);
|
||||
|
@ -747,7 +747,7 @@ namespace Test.Psi
|
|||
|
||||
try
|
||||
{
|
||||
var seq2 = Generators.Sequence(p2, 1, i => i + 1, count);
|
||||
var seq2 = Generators.Sequence(p2, 1, i => i + 1, count, TimeSpan.FromTicks(1));
|
||||
seq2.Do((m, e) =>
|
||||
{
|
||||
if (e.OriginatingTime.Ticks >= count / 2)
|
||||
|
@ -902,7 +902,7 @@ namespace Test.Psi
|
|||
using (var p = Pipeline.Create("write0"))
|
||||
{
|
||||
var writeStore = Store.Create(p, name0, this.path);
|
||||
var seq = Generators.Sequence(p, 1, i => i + 1, count);
|
||||
var seq = Generators.Sequence(p, 1, i => i + 1, count, TimeSpan.FromTicks(1));
|
||||
seq.Write("seq0", writeStore);
|
||||
p.Run();
|
||||
}
|
||||
|
@ -910,7 +910,7 @@ namespace Test.Psi
|
|||
using (var p = Pipeline.Create("write1"))
|
||||
{
|
||||
var writeStore = Store.Create(p, name1, this.path);
|
||||
var seq = Generators.Sequence(p, 1, i => i + 1, count);
|
||||
var seq = Generators.Sequence(p, 1, i => i + 1, count, TimeSpan.FromTicks(1));
|
||||
seq.Write("seq1", writeStore);
|
||||
p.Run();
|
||||
}
|
||||
|
@ -918,7 +918,7 @@ namespace Test.Psi
|
|||
using (var p = Pipeline.Create("write2"))
|
||||
{
|
||||
var writeStore = Store.Create(p, name2, this.path);
|
||||
var seq = Generators.Sequence(p, 1, i => i + 1, count);
|
||||
var seq = Generators.Sequence(p, 1, i => i + 1, count, TimeSpan.FromTicks(1));
|
||||
seq.Write("seq2", writeStore);
|
||||
p.Run();
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace Test.Psi
|
|||
using (var p2 = Pipeline.Create("b"))
|
||||
{
|
||||
var ready = new AutoResetEvent(false);
|
||||
var src = Generators.Sequence(p1, new[] { 1, 2, 3 });
|
||||
var src = Generators.Sequence(p1, new[] { 1, 2, 3 }, TimeSpan.FromTicks(1));
|
||||
var dest = new Processor<int, int>(p2, (i, e, o) => o.Post(i, e.OriginatingTime));
|
||||
dest.Do(i => ready.Set());
|
||||
var connector = new Connector<int>(p1, p2);
|
||||
|
@ -78,7 +78,7 @@ namespace Test.Psi
|
|||
using (var s = Subpipeline.Create(p, "sub"))
|
||||
{
|
||||
// add to sub-pipeline
|
||||
var seq = Generators.Sequence(s, new[] { 1, 2, 3 }).ToObservable().ToListObservable();
|
||||
var seq = Generators.Sequence(s, new[] { 1, 2, 3 }, TimeSpan.FromTicks(1)).ToObservable().ToListObservable();
|
||||
p.Run(); // run parent pipeline
|
||||
|
||||
Assert.IsTrue(Enumerable.SequenceEqual(new int[] { 1, 2, 3 }, seq.AsEnumerable()));
|
||||
|
@ -111,7 +111,7 @@ namespace Test.Psi
|
|||
{
|
||||
var doubler = new TestReactiveCompositeComponent(p);
|
||||
Assert.AreEqual(p, doubler.Out.Pipeline); // composite component shouldn't expose the fact that subpipeline is involved
|
||||
var seq = Generators.Sequence(p, new[] { 1, 2, 3 });
|
||||
var seq = Generators.Sequence(p, new[] { 1, 2, 3 }, TimeSpan.FromTicks(1));
|
||||
seq.PipeTo(doubler.In);
|
||||
var results = doubler.Out.ToObservable().ToListObservable();
|
||||
p.Run(); // note that parent pipeline stops once sources complete (reactive composite-component subpipeline doesn't "hold open")
|
||||
|
@ -129,7 +129,7 @@ namespace Test.Psi
|
|||
{
|
||||
var output = this.CreateOutputConnectorTo<int>(parent, "Output");
|
||||
this.Out = output.Out;
|
||||
Generators.Range(this, 0, 10).Out.PipeTo(output);
|
||||
Generators.Range(this, 0, 10, TimeSpan.FromTicks(1)).Out.PipeTo(output);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,7 +199,7 @@ namespace Test.Psi
|
|||
var connectorOut1 = subpipeline1.CreateOutputConnectorTo<int>(subpipeline0, "connectorOut1");
|
||||
|
||||
var results = new List<int>();
|
||||
Generators.Sequence(p, new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }).PipeTo(connectorIn0.In);
|
||||
Generators.Sequence(p, new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, TimeSpan.FromTicks(1)).PipeTo(connectorIn0.In);
|
||||
connectorIn0.Out.PipeTo(connectorIn1.In);
|
||||
connectorIn1.Out.PipeTo(connectorOut1.In);
|
||||
connectorOut1.Out.PipeTo(connectorOut0.In);
|
||||
|
@ -230,7 +230,7 @@ namespace Test.Psi
|
|||
Generators.Return(subpipeline1, 1);
|
||||
|
||||
var results = new List<int>();
|
||||
Generators.Sequence(p, new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }).PipeTo(connectorIn0.In);
|
||||
Generators.Sequence(p, new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, TimeSpan.FromTicks(1)).PipeTo(connectorIn0.In);
|
||||
connectorIn0.Out.PipeTo(connectorIn1.In);
|
||||
connectorIn1.Out.PipeTo(connectorOut1.In);
|
||||
connectorOut1.Out.PipeTo(connectorOut0.In);
|
||||
|
@ -261,7 +261,7 @@ namespace Test.Psi
|
|||
var infinite1 = new InfiniteTestComponent(subpipeline1);
|
||||
|
||||
var results = new List<int>();
|
||||
Generators.Sequence(p, new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }).PipeTo(connectorIn0.In);
|
||||
Generators.Sequence(p, new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, TimeSpan.FromTicks(1)).PipeTo(connectorIn0.In);
|
||||
connectorIn0.Out.PipeTo(connectorIn1.In);
|
||||
connectorIn1.Out.PipeTo(connectorOut1.In);
|
||||
connectorOut1.Out.PipeTo(connectorOut0.In);
|
||||
|
@ -315,7 +315,7 @@ namespace Test.Psi
|
|||
{
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
var p = Generators.Sequence(pipeline, new int[] { });
|
||||
var p = Generators.Sequence(pipeline, new int[] { }, TimeSpan.FromTicks(1));
|
||||
}
|
||||
|
||||
pipeline.Run();
|
||||
|
@ -555,12 +555,12 @@ namespace Test.Psi
|
|||
{
|
||||
using (var p = Pipeline.Create())
|
||||
{
|
||||
var gen = Generators.Range(p, 0, 10);
|
||||
var gen = Generators.Range(p, 0, 10, TimeSpan.FromTicks(1));
|
||||
p.RunAsync();
|
||||
Assert.IsFalse(p.WaitAll(0)); // running
|
||||
|
||||
// add generator while running
|
||||
Generators.Range(p, 0, 10);
|
||||
Generators.Range(p, 0, 10, TimeSpan.FromTicks(1));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1572,7 +1572,7 @@ namespace Test.Psi
|
|||
{
|
||||
var sub = Subpipeline.Create(p);
|
||||
var cIn = new Connector<int>(p, sub);
|
||||
var generator = Generators.Sequence(p, sequence);
|
||||
var generator = Generators.Sequence(p, sequence, TimeSpan.FromTicks(1));
|
||||
var receiver = sub.CreateReceiver<int>(collector, (d, e) => collector.Add(d), "Receiver");
|
||||
generator.PipeTo(cIn.In);
|
||||
cIn.Out.PipeTo(receiver);
|
||||
|
@ -1714,7 +1714,7 @@ namespace Test.Psi
|
|||
var log = new List<string>();
|
||||
PipelineDiagnostics graph = null;
|
||||
|
||||
using (var p = Pipeline.Create(true, TimeSpan.FromMilliseconds(1)))
|
||||
using (var p = Pipeline.Create(true, new DiagnosticsConfiguration() { SamplingInterval = TimeSpan.FromMilliseconds(1) }))
|
||||
{
|
||||
/*
|
||||
* .........
|
||||
|
@ -1743,6 +1743,24 @@ namespace Test.Psi
|
|||
while (graph == null) Thread.Sleep(10);
|
||||
}
|
||||
|
||||
Assert.AreEqual(2, graph.GetPipelineCount()); // total graphs
|
||||
Assert.AreEqual(11, graph.GetPipelineElementCount()); // total pipeline elements
|
||||
Assert.AreEqual(21, graph.GetEmitterCount()); // total emitters (not necessarily connected)
|
||||
Assert.AreEqual(6, graph.GetAllEmitterDiagnostics().Where(e => e.Targets.Count != 0).Count()); // total _connected_ emitters
|
||||
Assert.AreEqual(13, graph.GetReceiverCount()); // total receivers (not necessarily connected)
|
||||
Assert.AreEqual(6, graph.GetAllReceiverDiagnostics().Where(r => r.Source != null).Count()); // total _connected_ receivers
|
||||
Assert.IsTrue(graph.GetAllReceiverDiagnostics().Select(r => r.QueueSize).Sum() > 0); // usually 50+
|
||||
Assert.AreEqual(0, graph.GetDroppedMessageCount()); // total dropped
|
||||
Assert.AreEqual(0, graph.GetThrottledReceiverCount()); // total throttled receivers
|
||||
|
||||
// example complex query: average latency at emitter across reactive components in leaf subpipelines
|
||||
var complex = graph.GetAllPipelineDiagnostics()
|
||||
.Where(p => p.Subpipelines.Count == 0) // leaf subpipelines
|
||||
.GetAllPipelineElements()
|
||||
.Where(e => e.Kind == PipelineDiagnostics.PipelineElementDiagnostics.PipelineElementKind.Reactive) // reactive components
|
||||
.GetAllReceiverDiagnostics()
|
||||
.Select(r => r.MessageLatencyAtEmitterHistory.AverageTime()); // average latency at emitter into each component's receivers
|
||||
|
||||
Assert.AreEqual("default", graph.Name);
|
||||
Assert.IsTrue(graph.IsPipelineRunning);
|
||||
Assert.AreEqual(8, graph.PipelineElements.Count);
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace Test.Psi
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
[TestClass]
|
||||
public class SamplerTests
|
||||
public class SampleTests
|
||||
{
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
|
@ -19,14 +19,16 @@ namespace Test.Psi
|
|||
|
||||
using (var p = Pipeline.Create())
|
||||
{
|
||||
Generators.Sequence(p, 0, i => i + 1, 10, TimeSpan.FromTicks(100))
|
||||
.Sample(TimeSpan.FromTicks(50), Match.Best<int>(RelativeTimeInterval.RightBounded(TimeSpan.Zero)))
|
||||
var source = Generators.Sequence(p, 0, i => i + 1, 10, TimeSpan.FromTicks(50));
|
||||
var filtered = source.Where(i => i % 2 == 0);
|
||||
filtered
|
||||
.Sample(source, RelativeTimeInterval.Past())
|
||||
.Do(results.Add);
|
||||
|
||||
p.Run();
|
||||
}
|
||||
|
||||
CollectionAssert.AreEqual(new int[] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9 }, results);
|
||||
CollectionAssert.AreEqual(new int[] { 0, 0, 2, 2, 4, 4, 6, 6, 8, 8 }, results);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -37,8 +39,10 @@ namespace Test.Psi
|
|||
|
||||
using (var p = Pipeline.Create())
|
||||
{
|
||||
Generators.Sequence(p, 0, i => i + 1, 10, TimeSpan.FromTicks(100))
|
||||
.Sample(TimeSpan.FromTicks(300), Match.Best<int>(RelativeTimeInterval.RightBounded(TimeSpan.Zero)))
|
||||
var source = Generators.Sequence(p, 0, i => i + 1, 10, TimeSpan.FromTicks(50));
|
||||
var filtered = source.Where(i => i % 3 == 0);
|
||||
source
|
||||
.Sample(filtered, RelativeTimeInterval.Past())
|
||||
.Do(results.Add);
|
||||
|
||||
p.Run();
|
|
@ -48,7 +48,7 @@ namespace Test.Psi
|
|||
b.Emitter.Do(m => cLog.Enqueue(m));
|
||||
c.Emitter.Do(m => cLog.Enqueue(m));
|
||||
|
||||
Generators.Repeat(p, "PGen", 10).Do(m => cLog.Enqueue(m));
|
||||
Generators.Repeat(p, "PGen", 10, TimeSpan.FromTicks(1)).Do(m => cLog.Enqueue(m));
|
||||
|
||||
p.Run();
|
||||
}
|
||||
|
@ -109,9 +109,9 @@ namespace Test.Psi
|
|||
h.Emitter.Do(m => cLog.Enqueue(m));
|
||||
i.Emitter.Do(m => cLog.Enqueue(m));
|
||||
|
||||
Generators.Repeat(p, "PGen", 10).Do(m => cLog.Enqueue(m));
|
||||
Generators.Repeat(q, "QGen", 10).Do(m => cLog.Enqueue(m));
|
||||
Generators.Repeat(r, "RGen", 10).Do(m => cLog.Enqueue(m));
|
||||
Generators.Repeat(p, "PGen", 10, TimeSpan.FromTicks(1)).Do(m => cLog.Enqueue(m));
|
||||
Generators.Repeat(q, "QGen", 10, TimeSpan.FromTicks(1)).Do(m => cLog.Enqueue(m));
|
||||
Generators.Repeat(r, "RGen", 10, TimeSpan.FromTicks(1)).Do(m => cLog.Enqueue(m));
|
||||
|
||||
p.Run();
|
||||
}
|
||||
|
|
|
@ -283,7 +283,7 @@ namespace Test.Psi
|
|||
{
|
||||
var pool = new SharedPool<int[]>(() => new int[1], 1);
|
||||
var g = Generators
|
||||
.Range(p, 0, 10)
|
||||
.Range(p, 0, 10, TimeSpan.FromTicks(1))
|
||||
.Process<int, Shared<int[]>>(
|
||||
(i, e, emitter) =>
|
||||
{
|
||||
|
@ -310,7 +310,7 @@ namespace Test.Psi
|
|||
using (var p = Pipeline.Create())
|
||||
{
|
||||
var pool = new SharedPool<int[]>(() => new int[1], 1);
|
||||
var g = Generators.Range(p, 0, 10);
|
||||
var g = Generators.Range(p, 0, 10, TimeSpan.FromTicks(1));
|
||||
var s1 = g.Process<int, Shared<int[]>>(
|
||||
(i, e, emitter) =>
|
||||
{
|
||||
|
|
|
@ -459,50 +459,6 @@ namespace Test.Psi
|
|||
);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void StatisticsSumNullableDouble()
|
||||
{
|
||||
this.RunTest(Operators.Sum,
|
||||
(
|
||||
new double?[] { }, // empty sequence
|
||||
new double?[] { } // expected output
|
||||
),
|
||||
(
|
||||
new double?[] { null, null, null }, // null sequence
|
||||
new double?[] { 0, 0, 0 } // expected output
|
||||
),
|
||||
(
|
||||
new double?[] { -1.0, -2.0, -3.0, 0.0, 1.0, 2.0 }, // real numbers only
|
||||
new double?[] { -1.0, -3.0, -6.0, -6.0, -5.0, -3.0 } // expected output
|
||||
),
|
||||
(
|
||||
new double?[] { -1.0, -2.0, null, -3.0, 0.0, 1.0, null, 2.0 }, // sequence with null values
|
||||
new double?[] { -1.0, -3.0, -3.0, -6.0, -6.0, -5.0, -5.0, -3.0 } // expected output
|
||||
),
|
||||
(
|
||||
new double?[] { 0.0, double.NaN, 1.0, 0.0 }, // sequence with NaN
|
||||
new double?[] { 0.0, double.NaN, double.NaN, double.NaN } // expected output
|
||||
),
|
||||
(
|
||||
new double?[] { double.NaN, 0.0, 1.0, 0.0 }, // first element is NaN
|
||||
new double?[] { double.NaN, double.NaN, double.NaN, double.NaN } // expected output
|
||||
),
|
||||
(
|
||||
new double?[] { 0.0, 1.0, 0.0, double.NaN }, // last element is NaN
|
||||
new double?[] { 0.0, 1.0, 1.0, double.NaN } // expected output
|
||||
),
|
||||
(
|
||||
new double?[] { double.NaN, double.NaN, double.NaN, double.NaN }, // sequence contains only NaNs
|
||||
new double?[] { double.NaN, double.NaN, double.NaN, double.NaN } // expected output
|
||||
),
|
||||
(
|
||||
new double?[] { double.NegativeInfinity, -1.0, double.PositiveInfinity, 1.0, double.NaN }, // sequence with +/- infinity
|
||||
new double?[] { double.NegativeInfinity, double.NegativeInfinity, double.NaN, double.NaN, double.NaN } // expected output
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void StatisticsSumIEnumerableDouble()
|
||||
|
@ -580,50 +536,6 @@ namespace Test.Psi
|
|||
);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void StatisticsSumNullableFloat()
|
||||
{
|
||||
this.RunTest(Operators.Sum,
|
||||
(
|
||||
new float?[] { }, // empty sequence
|
||||
new float?[] { } // expected output
|
||||
),
|
||||
(
|
||||
new float?[] { null, null, null }, // null sequence
|
||||
new float?[] { 0, 0, 0 } // expected output
|
||||
),
|
||||
(
|
||||
new float?[] { -1.0f, -2.0f, -3.0f, 0.0f, 1.0f, 2.0f }, // real numbers only
|
||||
new float?[] { -1.0f, -3.0f, -6.0f, -6.0f, -5.0f, -3.0f } // expected output
|
||||
),
|
||||
(
|
||||
new float?[] { -1.0f, -2.0f, null, -3.0f, 0.0f, 1.0f, null, 2.0f }, // sequence with null values
|
||||
new float?[] { -1.0f, -3.0f, -3.0f, -6.0f, -6.0f, -5.0f, -5.0f, -3.0f } // expected output
|
||||
),
|
||||
(
|
||||
new float?[] { 0.0f, float.NaN, 1.0f, 0.0f }, // sequence with NaN
|
||||
new float?[] { 0.0f, float.NaN, float.NaN, float.NaN } // expected output
|
||||
),
|
||||
(
|
||||
new float?[] { float.NaN, 0.0f, 1.0f, 0.0f }, // first element is NaN
|
||||
new float?[] { float.NaN, float.NaN, float.NaN, float.NaN } // expected output
|
||||
),
|
||||
(
|
||||
new float?[] { 0.0f, 1.0f, 0.0f, float.NaN }, // last element is NaN
|
||||
new float?[] { 0.0f, 1.0f, 1.0f, float.NaN } // expected output
|
||||
),
|
||||
(
|
||||
new float?[] { float.NaN, float.NaN, float.NaN, float.NaN }, // sequence contains only NaNs
|
||||
new float?[] { float.NaN, float.NaN, float.NaN, float.NaN } // expected output
|
||||
),
|
||||
(
|
||||
new float?[] { float.NegativeInfinity, -1.0f, float.PositiveInfinity, 1.0f, float.NaN }, // sequence with +/- infinity
|
||||
new float?[] { float.NegativeInfinity, float.NegativeInfinity, float.NaN, float.NaN, float.NaN } // expected output
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void StatisticsSumIEnumerableFloat()
|
||||
|
@ -874,7 +786,7 @@ namespace Test.Psi
|
|||
new[] { double.NegativeInfinity, 2.0, -1.5, 3.0 },
|
||||
new[] { double.PositiveInfinity, 2.0, -1.5, 3.0 }
|
||||
},
|
||||
new[] { 0, double.NaN, 0.70710678118654757, 1.9311050377094112, double.NaN, double.NaN, double.NaN, double.NaN } // expected output
|
||||
new[] { 0, 0, 0.70710678118654757, 1.9311050377094112, double.NaN, double.NaN, double.NaN, double.NaN } // expected output
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -932,7 +844,75 @@ namespace Test.Psi
|
|||
new[] { float.NegativeInfinity, 2.0f, -1.5f, 3.0f },
|
||||
new[] { float.PositiveInfinity, 2.0f, -1.5f, 3.0f }
|
||||
},
|
||||
new[] { 0f, float.NaN, 0.707106769f, 1.931105f, float.NaN, float.NaN, float.NaN, float.NaN } // expected output
|
||||
new[] { 0f, 0f, 0.707106769f, 1.931105f, float.NaN, float.NaN, float.NaN, float.NaN } // expected output
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void StatisticsStdDecimal()
|
||||
{
|
||||
this.RunTest(Operators.Std,
|
||||
(
|
||||
new decimal[] { }, // empty sequence
|
||||
new decimal[] { } // expected output
|
||||
),
|
||||
(
|
||||
new[] { -1.0m, -2.0m, -3.0m, 0.0m, 1.0m, 2.0m }, // real numbers only
|
||||
new[] { 0.0m, 0.707106781186548m, 1m, 1.29099444873581m, 1.58113883008419m, 1.87082869338697m } // expected output
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void StatisticsStdIEnumerableDecimal()
|
||||
{
|
||||
this.RunTest(Operators.Std,
|
||||
(
|
||||
new IEnumerable<decimal>[] // sequence of enumerations
|
||||
{
|
||||
new decimal[] { },
|
||||
new[] { 1.0m },
|
||||
new[] { 1.0m, 2.0m },
|
||||
new[] { 1.0m, 2.0m, -1.5m, 3.0m },
|
||||
},
|
||||
new[] { 0m, 0m, 0.707106781186548m, 1.93110503770941m } // expected output
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void StatisticsStdInt()
|
||||
{
|
||||
this.RunTest(Operators.Std,
|
||||
(
|
||||
new int[] { }, // empty sequence
|
||||
new double[] { } // expected output
|
||||
),
|
||||
(
|
||||
new[] { -1, -2, -3, 0, 1, 2 },
|
||||
new[] { double.NaN, 0.70710678118654757, 1, 1.2909944487358056, 1.5811388300841898, 1.8708286933869707 } // expected output
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void StatisticsStdIEnumerableInt()
|
||||
{
|
||||
this.RunTest(Operators.Std,
|
||||
(
|
||||
new IEnumerable<int>[] // sequence of enumerations
|
||||
{
|
||||
new int[] { },
|
||||
new[] { 1 },
|
||||
new[] { 1, 2 },
|
||||
new[] { 1, 2, -1, 3 },
|
||||
},
|
||||
new[] { 0, 0, 0.70710678118654757, 1.707825127659933 } // expected output
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -959,7 +939,7 @@ namespace Test.Psi
|
|||
{
|
||||
var output = new List<TOutput>();
|
||||
outputs.Add(output);
|
||||
@operator(Generators.Sequence(p, inputSequence), null).Do(x => output.Add(x));
|
||||
@operator(Generators.Sequence(p, inputSequence, TimeSpan.FromTicks(1)), null).Do(x => output.Add(x));
|
||||
}
|
||||
|
||||
p.Run(null, true);
|
||||
|
|
|
@ -204,9 +204,9 @@ namespace Test.Psi
|
|||
|
||||
source.Parallel(s => s.Select(val => val / 100), true)
|
||||
.Do(x => resultsDefault.Add(x.DeepClone()));
|
||||
source.Parallel(s => s.Select(val => val / 100), true, null, BranchTerminationPolicy<int, int>.AfterKeyNotPresent(1))
|
||||
source.Parallel(s => s.Select(val => val / 100), true, branchTerminationPolicy: BranchTerminationPolicy<int, int>.AfterKeyNotPresent(1))
|
||||
.Do(x => resultsAfterKeyNotPresentOnce.Add(x.DeepClone()));
|
||||
source.Parallel(s => s.Select(val => val / 100), true, null, BranchTerminationPolicy<int, int>.Never())
|
||||
source.Parallel(s => s.Select(val => val / 100), true, branchTerminationPolicy: BranchTerminationPolicy<int, int>.Never())
|
||||
.Do(x => resultsNever.Add(x.DeepClone()));
|
||||
|
||||
p.Run();
|
||||
|
@ -363,7 +363,7 @@ namespace Test.Psi
|
|||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void SparseVectorTestJoinOrDefault()
|
||||
public void SparseVectorTestJoinOutputDefaultIfDropped()
|
||||
{
|
||||
// I'm generating a dictionary with 2 keys (1 and 2) that over time
|
||||
// streams like this
|
||||
|
@ -374,11 +374,11 @@ namespace Test.Psi
|
|||
// So with drops things look like this
|
||||
// 1: 0 . 2 . 4 . 6 . 8 .
|
||||
// 2: 0 2 4 6 8 10 12 14 16 18
|
||||
// If parallel is using the regular Join (instead of JoinOrDefault)
|
||||
// If parallel is using the regular Join (instead of outputDefaultIfDropped: true)
|
||||
// then we should have 5 messages out, like this
|
||||
// 1: 0 2 4 6 8
|
||||
// 2: 0 4 8 12 16
|
||||
// When parallel operates correctly with a JoinOrDefault, we should get
|
||||
// When parallel operates correctly with a outputDefaultIfDropped: true, we should get
|
||||
// all ten messages out, where the dropped messages are replaced with
|
||||
// default(int) which is 0, so like this.
|
||||
// 1: 0 0 2 0 4 0 6 0 8 0
|
||||
|
@ -442,7 +442,7 @@ namespace Test.Psi
|
|||
|
||||
[TestMethod]
|
||||
[Timeout(60000)]
|
||||
public void SparseVectorWithGammaCreatingHolesAndJoinOrDefault()
|
||||
public void SparseVectorWithGammaCreatingHolesAndOutputDefaultIfDropped()
|
||||
{
|
||||
var frames =
|
||||
new[]
|
||||
|
@ -484,7 +484,7 @@ namespace Test.Psi
|
|||
|
||||
p.RunAsync();
|
||||
|
||||
Action<int> step = (expected) =>
|
||||
void step(int expected)
|
||||
{
|
||||
stepper.Step();
|
||||
while (results.Count != expected)
|
||||
|
@ -493,7 +493,7 @@ namespace Test.Psi
|
|||
}
|
||||
|
||||
Assert.AreEqual<int>(expected, results.Count);
|
||||
};
|
||||
}
|
||||
|
||||
Assert.AreEqual<int>(0, results.Count);
|
||||
|
||||
|
|
|
@ -11,6 +11,6 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation. All rights reserved.")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("fffd905a-1672-4920-b790-eea6a961383c")]
|
||||
[assembly: AssemblyVersion("0.9.6.1")]
|
||||
[assembly: AssemblyFileVersion("0.9.6.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.9.6.1-beta")]
|
||||
[assembly: AssemblyVersion("0.10.16.1")]
|
||||
[assembly: AssemblyFileVersion("0.10.16.1")]
|
||||
[assembly: AssemblyInformationalVersion("0.10.16.1-beta")]
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче