diff --git a/.editorconfig b/.editorconfig index 9263be7..876b864 100644 --- a/.editorconfig +++ b/.editorconfig @@ -361,7 +361,7 @@ dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibi dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style -dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity = error +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity = none # Private fields must be camelCase # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1306.md diff --git a/StyleCop.xml b/StyleCop.xml index cd0b013..354d234 100644 --- a/StyleCop.xml +++ b/StyleCop.xml @@ -11,5 +11,6 @@ + diff --git a/src/DxFeed.Graal.Net/Models/AbstractTxModel.cs b/src/DxFeed.Graal.Net/Models/AbstractTxModel.cs new file mode 100644 index 0000000..e18ec3c --- /dev/null +++ b/src/DxFeed.Graal.Net/Models/AbstractTxModel.cs @@ -0,0 +1,178 @@ +// +// Copyright © 2024 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using System; +using DxFeed.Graal.Net.Api; +using DxFeed.Graal.Net.Events; +using DxFeed.Graal.Net.Native.Model; + +namespace DxFeed.Graal.Net.Models; + +/// +/// Abstract base class for models that handle transactions of . +/// This class manages all snapshot and transaction logic, subscription handling, and listener notifications. +/// +/// +/// This model is designed to handle incremental transactions. Users of this model only see the list +/// of events in a consistent state. This model delays incoming events that are part of an incomplete snapshot +/// or ongoing transaction until the snapshot is complete or the transaction has ended. +/// +///

Configuration

+/// This model must be configured using the . Specific implementations can add additional +/// configuration options as needed. +/// +///

Resource management and closed models

+/// Attached model is a potential memory leak. If the pointer to attached model is lost, then there is no way +/// to detach this model from the feed and the model will not be reclaimed by the garbage collector as long as the +/// corresponding feed is still used. Detached model can be reclaimed by the garbage collector, but detaching model +/// requires knowing the pointer to the feed at the place of the call, which is not always convenient. +/// +/// The convenient way to detach model from the feed is to call its method. Closed model +/// becomes permanently detached from all feeds, removes all its listeners and is guaranteed to be reclaimable by +/// the garbage collector as soon as all external references to it are cleared. +/// +///

Threads and locks

+/// This class is thread-safe and can be used concurrently from multiple threads without external synchronization. +///
+public abstract class AbstractTxModel +{ + private protected readonly AbstractTxModelHandle handle; + + private protected AbstractTxModel(AbstractTxModelHandle handle) => + this.handle = handle; + + /// + /// Gets whether batch processing is enabled. + /// See . + /// + /// + /// true if batch processing is enabled; otherwise, false. + /// + public bool IsBatchProcessing() => + handle.IsBatchProcessing(); + + /// + /// Gets whether snapshot processing is enabled. + /// See . + /// + /// + /// true if snapshot processing is enabled; otherwise, false. + /// + public bool IsSnapshotProcessing() => + handle.IsSnapshotProcessing(); + + /// + /// Closes this model and makes it permanently detached. + /// + /// + /// This method clears installed listeners and ensures that the model + /// can be safely garbage-collected when all outside references to it are lost. + /// + public void Close() => + handle.Close(); + + /// + /// Abstract builder for building models inherited from . + /// Specific implementations can add additional configuration options to this builder. + /// + /// + /// Inheritors of this class must override the abstract method to build a specific model. + /// + /// The type of the builder subclass. + /// The type of the model subclass. + public abstract class Builder + where TB : Builder + where TM : AbstractTxModel + { + /// + /// Initializes a new instance of the class with the specified event type. + /// + /// The type of events processed by the model being created. + protected Builder(Type eventType) + { + } + + /// + /// Enables or disables batch processing. This is enabled by default. + /// + /// + /// If batch processing is disabled, the model will notify the listener + /// separately for each transaction (even if it is represented by a single event); + /// otherwise, transactions can be combined in a single listener call. + /// + ///
A transaction may represent either a snapshot or update events that are received after a snapshot. + /// Whether this flag is set or not, the model will always notify listeners that a snapshot has been received + /// and will not combine multiple snapshots or a snapshot with another transaction + /// into a single listener notification. + ///
+ /// true to enable batch processing; false otherwise. + /// The builder instance. + public TB WithBatchProcessing(bool isBatchProcessing) + { + return (TB)this; + } + + /// + /// Enables or disables snapshot processing. This is disabled by default. + /// + /// + /// If snapshot processing is enabled, transactions representing a snapshot will be processed as follows: + /// events that are marked for removal will be removed, repeated indexes will be merged, and + /// event flags of events are set to zero; otherwise, the user will see the snapshot in raw form, + /// with possible repeated indexes, events marked for removal, and event flags unchanged. + /// + /// Whether this flag is set or not, in transactions that are not a snapshot, events that are marked + /// for removal will not be removed, repeated indexes will not be merged, and + /// event flags of events will not be changed. + /// This flag only affects the processing of transactions that are a snapshot. + /// + /// true to enable snapshot processing; false otherwise. + /// The builder instance. + public TB WithSnapshotProcessing(bool isSnapshotProcessing) + { + return (TB)this; + } + + /// + /// Sets the for the model being created. + /// The feed cannot be attached after the model has been built. + /// + /// The . + /// The builder instance. + public TB WithFeed(DXFeed feed) + { + return (TB)this; + } + + /// + /// Sets the subscription symbol for the model being created. + /// The symbol cannot be added or changed after the model has been built. + /// + /// The subscription symbol. + /// The builder instance. + public TB WithSymbol(object symbol) + { + return (TB)this; + } + + /// + /// Sets the listener for transaction notifications. + /// The listener cannot be changed or added once the model has been built. + /// + /// The transaction listener. + /// The builder instance. + public TB WithListener(TxModelListener listener) + { + return (TB)this; + } + + /// + /// Builds an instance of the model based on the provided parameters. + /// + /// The created model. + public abstract TM Build(); + } +} diff --git a/src/DxFeed.Graal.Net/Models/IndexedTxModel.cs b/src/DxFeed.Graal.Net/Models/IndexedTxModel.cs new file mode 100644 index 0000000..4010b4d --- /dev/null +++ b/src/DxFeed.Graal.Net/Models/IndexedTxModel.cs @@ -0,0 +1,151 @@ +// +// Copyright © 2024 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using System; +using System.Collections.Generic; +using DxFeed.Graal.Net.Api; +using DxFeed.Graal.Net.Events; +using DxFeed.Graal.Net.Native.Model; + +namespace DxFeed.Graal.Net.Models; + +/// +/// An incremental model for indexed events. +/// This model manages all snapshot and transaction logic, subscription handling, and listener notifications. +/// +/// +/// +/// This model is designed to handle incremental transactions. Users of this model only see the list +/// of events in a consistent state. This model delays incoming events that are part of an incomplete snapshot +/// or ongoing transaction until the snapshot is complete or the transaction has ended. This model notifies +/// the user of received transactions through an installed listener. +/// +///

Configuration

+/// +/// This model must be configured using the class, as most configuration +/// settings cannot be changed once the model is built. This model requires configuration with +/// a symbol and +/// a sources +/// for subscription, and it must be attached to a +/// instance to begin operation. +/// For ease of use, some of these configurations can be changed after the model is built, see +/// . +/// +/// +/// This model only supports single symbol subscriptions; multiple symbols cannot be configured. +/// +///

Resource management and closed models

+/// +/// Attached model is a potential memory leak. If the pointer to attached model is lost, then there is no way +/// to detach this model from the feed and the model will not be reclaimed by the garbage collector as long as the +/// corresponding feed is still used. Detached model can be reclaimed by the garbage collector, but detaching model +/// requires knowing the pointer to the feed at the place of the call, which is not always convenient. +/// +/// +/// The convenient way to detach model from the feed is to call its method. +/// Closed model becomes permanently detached from all feeds, removes all its listeners and is guaranteed +/// to be reclaimable by the garbage collector as soon as all external references to it are cleared. +/// +///

Threads and locks

+/// +/// This class is thread-safe and can be used concurrently from multiple threads without external synchronization. +/// +///
+public sealed class IndexedTxModel : AbstractTxModel +{ + private IndexedTxModel(AbstractTxModelHandle handle) + : base(handle) + { + } + + /// + /// Factory method to create a new builder for this model. + /// + /// The class type of the time series event. + /// A new instance. + public static Builder NewBuilder(Type eventType) => + new(eventType); + + /// + /// Returns the current set of sources. + /// If no sources have been set, an empty set is returned, + /// indicating that all possible sources have been subscribed to. + /// + /// The set of current sources. + public HashSet GetSources() => + ((IndexedTxModelHandle)handle).GetSources(); + + /// + /// Sets the sources from which to subscribe for indexed events. + /// If an empty list is provided, subscriptions will default to all available sources. + /// If these sources have already been set, nothing happens. + /// + /// The specified sources. + public void SetSources(params IndexedEventSource[] sources) => + ((IndexedTxModelHandle)handle).SetSources(sources); + + /// + /// Sets the sources from which to subscribe for indexed events. + /// If an empty set is provided, subscriptions will default to all available sources. + /// If these sources have already been set, nothing happens. + /// + /// The specified sources. + public void SetSources(ICollection sources) => + ((IndexedTxModelHandle)handle).SetSources(sources); + + /// + /// A builder class for creating an instance of . + /// + public class Builder : Builder + { + /// + /// Initializes a new instance of the class with the specified event type. + /// + /// The type of events processed by the model being created. + public Builder(Type eventType) + : base(eventType) + { + } + + /// + /// Sets the sources from which to subscribe for indexed events. + /// If no sources have been set, subscriptions will default to all possible sources. + /// + /// + /// The default value for this source is an empty set, + /// which means that this model subscribes to all available sources. + /// These sources can be changed later, after the model has been created, + /// by calling . + /// + /// The specified sources. + /// The builder instance. + public Builder WithSources(params IndexedEventSource[] sources) + { + return this; + } + + /// + /// Sets the sources from which to subscribe for indexed events. + /// If no sources have been set, subscriptions will default to all possible sources. + /// + /// + /// The default value for this source is an empty set, + /// which means that this model subscribes to all available sources. + /// These sources can be changed later, after the model has been created, + /// by calling . + /// + /// The specified sources. + /// The builder instance. + public Builder WithSources(ICollection sources) + { + return this; + } + + /// + public override IndexedTxModel Build() => + throw new NotImplementedException(); + } +} diff --git a/src/DxFeed.Graal.Net/Models/TimeSeriesTxModel.cs b/src/DxFeed.Graal.Net/Models/TimeSeriesTxModel.cs new file mode 100644 index 0000000..203325b --- /dev/null +++ b/src/DxFeed.Graal.Net/Models/TimeSeriesTxModel.cs @@ -0,0 +1,118 @@ +// +// Copyright © 2024 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using System; +using DxFeed.Graal.Net.Api; +using DxFeed.Graal.Net.Native.Model; + +namespace DxFeed.Graal.Net.Models; + +/// +/// An incremental model for time-series events. +/// This model manages all snapshot and transaction logic, subscription handling, and listener notifications. +/// +/// +/// +/// This model is designed to handle incremental transactions. Users of this model only see the list +/// of events in a consistent state. This model delays incoming events that are part of an incomplete snapshot +/// or ongoing transaction until the snapshot is complete or the transaction has ended. This model notifies +/// the user of received transactions through an installed listener. +/// +///

Configuration

+/// +/// This model must be configured using the class, as most configuration +/// settings cannot be changed once the model is built. This model requires configuration with +/// a symbol and a +/// fromTime for subscription, and it must be attached +/// to a instance to begin operation. +/// For ease of use, some of these configurations can be changed after the model is built, see +/// . +/// +/// +/// This model only supports single symbol subscriptions; multiple symbols cannot be configured. +/// +///

Resource management and closed models

+/// +/// Attached model is a potential memory leak. If the pointer to the attached model is lost, then there is no way +/// to detach this model from the feed and the model will not be reclaimed by the garbage collector as long as the +/// corresponding feed is still used. Detached model can be reclaimed by the garbage collector, but detaching the model +/// requires knowing the pointer to the feed at the place of the call, which is not always convenient. +/// +/// +/// The convenient way to detach the model from the feed is to call its method. +/// Closed model becomes permanently detached from all feeds, removes all its listeners and is guaranteed +/// to be reclaimable by the garbage collector as soon as all external references to it are cleared. +/// +///

Threads and locks

+/// +/// This class is thread-safe and can be used concurrently from multiple threads without external synchronization. +/// +///
+public class TimeSeriesTxModel : AbstractTxModel +{ + private TimeSeriesTxModel(AbstractTxModelHandle handle) + : base(handle) + { + } + + /// + /// Factory method to create a new builder for this model. + /// + /// The class type of the time series event. + /// A new instance. + public static Builder NewBuilder(Type eventType) => + new(eventType); + + /// + /// Returns the time from which to subscribe for time-series, + /// or if this model is not subscribed (this is the default value). + /// + /// The time in milliseconds since Unix epoch of January 1, 1970. + public long GetFromTime() => + ((TimeSeriesTxModelHandle)handle).GetFromTime(); + + /// + /// Sets the time from which to subscribe for time-series. + /// If this time has already been set, nothing happens. + /// + /// The time in milliseconds since Unix epoch of January 1, 1970. + public void SetFromTime(long fromTime) => + ((TimeSeriesTxModelHandle)handle).SetFromTime(fromTime); + + /// + /// A builder class for creating an instance of . + /// + public class Builder : Builder + { + /// + /// Initializes a new instance of the class with the specified event type. + /// + /// The type of events processed by the model being created. + public Builder(Type eventType) + : base(eventType) + { + } + + /// + /// Sets the time from which to subscribe for time-series. + /// + /// + /// This time defaults to , which means that this model is not subscribed. + /// This time can be changed later, after the model has been created, + /// by calling . + /// + /// The time in milliseconds since Unix epoch of January 1, 1970. + /// The builder instance. + public Builder WithFromTime(long fromTime) + { + return this; + } + + /// + public override TimeSeriesTxModel Build() => + throw new NotImplementedException(); + } +} diff --git a/src/DxFeed.Graal.Net/Models/TxModelListener.cs b/src/DxFeed.Graal.Net/Models/TxModelListener.cs new file mode 100644 index 0000000..897d303 --- /dev/null +++ b/src/DxFeed.Graal.Net/Models/TxModelListener.cs @@ -0,0 +1,13 @@ +// +// Copyright © 2024 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using System.Collections.Generic; +using DxFeed.Graal.Net.Events; + +namespace DxFeed.Graal.Net.Models; + + +public delegate void TxModelListener(IndexedEventSource source, IEnumerable events, bool isSnapshot); diff --git a/src/DxFeed.Graal.Net/Native/Model/AbstractTxModelHandle.cs b/src/DxFeed.Graal.Net/Native/Model/AbstractTxModelHandle.cs new file mode 100644 index 0000000..6355b58 --- /dev/null +++ b/src/DxFeed.Graal.Net/Native/Model/AbstractTxModelHandle.cs @@ -0,0 +1,22 @@ +// +// Copyright © 2024 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using DxFeed.Graal.Net.Native.Interop; + +namespace DxFeed.Graal.Net.Native.Model; + +internal class AbstractTxModelHandle : JavaHandle +{ + public bool IsBatchProcessing() => + false; + + public bool IsSnapshotProcessing() => + false; + + public void Close() + { + } +} diff --git a/src/DxFeed.Graal.Net/Native/Model/IndexedTxModelHandle.cs b/src/DxFeed.Graal.Net/Native/Model/IndexedTxModelHandle.cs new file mode 100644 index 0000000..ff80a24 --- /dev/null +++ b/src/DxFeed.Graal.Net/Native/Model/IndexedTxModelHandle.cs @@ -0,0 +1,23 @@ +// +// Copyright © 2024 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using System; +using System.Collections.Generic; +using DxFeed.Graal.Net.Events; + +namespace DxFeed.Graal.Net.Native.Model; + +internal class IndexedTxModelHandle : AbstractTxModelHandle +{ + public HashSet GetSources() => + throw new NotImplementedException(); + + public void SetSources(params IndexedEventSource[] sources) => + throw new NotImplementedException(); + + public void SetSources(ICollection sources) => + throw new NotImplementedException(); +} diff --git a/src/DxFeed.Graal.Net/Native/Model/TimeSeriesTxModelHandle.cs b/src/DxFeed.Graal.Net/Native/Model/TimeSeriesTxModelHandle.cs new file mode 100644 index 0000000..3304392 --- /dev/null +++ b/src/DxFeed.Graal.Net/Native/Model/TimeSeriesTxModelHandle.cs @@ -0,0 +1,18 @@ +// +// Copyright © 2024 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using System; + +namespace DxFeed.Graal.Net.Native.Model; + +internal class TimeSeriesTxModelHandle : AbstractTxModelHandle +{ + public long GetFromTime() => + throw new NotImplementedException(); + + public void SetFromTime(long fromTime) => + throw new NotImplementedException(); +} diff --git a/src/DxFeed.Graal.Net/Native/Model/TxEventListenerHandle.cs b/src/DxFeed.Graal.Net/Native/Model/TxEventListenerHandle.cs new file mode 100644 index 0000000..5e9ba8d --- /dev/null +++ b/src/DxFeed.Graal.Net/Native/Model/TxEventListenerHandle.cs @@ -0,0 +1,63 @@ +// +// Copyright © 2024 Devexperts LLC. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +using System; +using System.Linq; +using System.Runtime.InteropServices; +using DxFeed.Graal.Net.Api; +using DxFeed.Graal.Net.Events; +using DxFeed.Graal.Net.Events.Market; +using DxFeed.Graal.Net.Models; +using DxFeed.Graal.Net.Native.Endpoint; +using DxFeed.Graal.Net.Native.Events; +using DxFeed.Graal.Net.Native.Interop; +using static DxFeed.Graal.Net.Native.ErrorHandling.ErrorCheck; + +namespace DxFeed.Graal.Net.Native.Model; + +internal class TxEventListenerHandle : JavaHandle +{ + private static readonly unsafe Delegate OnEventsReceivedDelegate = new OnEventsReceivedDelegateType(OnEventReceived); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private unsafe delegate void OnEventsReceivedDelegateType(IntPtr thread, int source, ListNative* events, bool isSnapshot, GCHandle handle); + + public static TxEventListenerHandle Create(TxModelListener listener) => + CreateAndRegisterFinalize(listener, handle => SafeCall(Import.New(CurrentThread, OnEventsReceivedDelegate, handle))); + + private static unsafe void OnEventReceived(IntPtr thread, int source, ListNative* events, bool isSnapshot, GCHandle handle) + { + if (!handle.IsAllocated) + { + return; + } + + var listener = handle.Target as TxModelListener; + try + { + var eventList = EventMapper.FromNative(events); + listener?.Invoke(OrderSource.ValueOf(source), eventList.OfType(), isSnapshot); + } + catch (Exception e) + { + // ToDo Add log entry. + Console.Error.WriteLine($"Exception in user {nameof(TxModelListener)}. {e}"); + } + } + + private static class Import + { + [DllImport( + ImportInfo.DllName, + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + EntryPoint = "dxfg_PropertyChangeListener_new")] + public static extern TxEventListenerHandle New( + nint thread, + Delegate listener, + GCHandle handle); + } +}