• Avalanche.Core
Search Results for

    Show / Hide Table of Contents
    • Avalanche.Accessor
      • Introduction
      • IAccessor
        • IAccessor
        • IFieldAccessor
        • IListAccessor
        • IMapAccessor
        • IRecordAccessor
        • IOneOfAccessor
        • IAnyAccessor
      • Dto
        • Introduction
        • IList<T>
        • IDictionary<K,V>
        • FieldInfo
        • OneOfAttribute
        • StructLayoutAttribute
        • Class
      • Articles
        • Dependency Injection
        • AccessorMessages
    • Avalanche.Binding
      • Introduction
    • Avalanche.Configuration
      • Introduction
      • Configuration Binding
      • ConfigurationExtensions
      • MemoryConfiguration
      • PrintTree
      • Saving IOptions
      • Yaml
    • Avalanche.Converter
      • Introduction
      • EnumConverter
      • Func<,>
      • HexConverter
      • PrimitiveConverter
      • StringConverter
    • Avalanche.Core
      • License
    • Avalanche.DataType
      • Introduction
      • DataType
        • IDataType
        • IListType
        • IMapType
        • IRecordType
        • IFieldType
        • IOneOfType
        • IAnyType
        • IStringType
        • IValueType
        • IIntegerType
        • IEnumerationType
        • IFloatingPointType
      • Dto
        • Introduction
        • IList<T>
        • IDictionary<K,V>
        • FieldInfo
        • Enum
        • OneOfAttribute
        • StructLayoutAttribute
        • Class
        • DtoTarget(IDataType)
        • IntermediateTarget
      • Articles
        • DataTypeRequest
        • PrintTree
        • DataTypeMessages
    • Avalanche.Emit
      • Introduction
      • TypeBuilder
      • ConstructorBuilder
      • MethodBuilder
      • PropertyBuilder
      • FieldBuilder
      • Emit
      • Utilities
        • CustomAttributeUtilities
        • ToStringRequest
    • Avalanche.FileSystem
      • Introduction
      • Abstractions
        • IFileSystemBrowse
        • IFileSystemCreateDirectory
        • IFileSystemDelete
        • IFileSystemFileAttribute
        • IFileSystemMount
        • IFileSystemMove
        • IFileSystemObserve
        • IFileSystemOpenStream
        • IFileSystemOpenMemory
        • IEvent
        • IEntry
        • IOption
        • IToken
      • FileSystem
      • VirtualFileSystem
      • MemoryFileSystem
      • EmbeddedFileSystem
      • HttpFileSystem
      • Decoration
      • IFileProvider
      • Utilities
        • Dispose
        • FileDictionary
        • FileScanner
        • FilterEnumerable
        • OperationSession
        • PollingFilterWatchToken
        • VisitTree
      • Article
        • Examples
        • PrintTree
    • Avalanche.Identity
      • Introduction
      • Identity
      • IdentityParts
      • IdentityInterner
      • IdentityComparer
      • Print Tree
      • IdentityAccessors
        • Introduction
        • TypeName
    • Avalanche.Localization
      • Introduction
      • Localization
      • LocalizationFile
      • LocalizationFiles
      • LocalizationFileSystem
      • LocalizationFileFormat
      • LocalizationLine
      • LocalizationLines
      • TemplateFormat
      • CultureProvider
      • FallbackCultureProvider
      • ResourceManager
      • LocalizationError
      • Microsoft.Extensions
        • Introduction
        • DependencyInjection
        • FileProvider
        • Logging
        • ITextLocalizer
        • IFileLocalizer
        • Localization
      • Asp.Net
        • Introduction
        • Supplying localization
        • Inject to pages
        • Culture Assigned
        • Minimalistic Api
        • Diagnostics
      • Pluralization
        • Introduction
        • Multiple plural parameters
        • Custom PluralRules
        • Invariant Culture
        • Unit Prefix
        • IPluralRule
        • IPluralNumber
        • IPluralRules
        • CLDRs
        • Unicode.CLDR40
        • Unicode.CLDR41
        • Unicode.CLDR42
        • Unicode.CLDR43
      • Articles
        • Alphabet localization
        • Benchmarks
        • Caching
        • Class Library
        • Demo
        • Diagnostics
        • Embedded resources
        • Emplacement
        • File localization
        • Text localization
        • Printing templates
    • Avalanche.Memory
      • Introduction
      • IMemory<T>
      • BlockMemory<T>
      • FileMemory
      • ListMemory<L, T>
      • MemoryMemory<T>
      • MMFMemory<T>
      • PointerMemory<T>
      • Slice<M, T>
      • StreamMemory
      • Concurrency
        • RWLockedMemory
        • SynchronizedMemory
      • Serialization
        • Benchmarks
        • Fixed<T>
        • Variable<T>
        • ZigZag<T>
        • StringEncoding
      • Articles
        • Fixed-size and Variable-sized
        • IBlockPool<T>
        • Print Tree
        • Read and Write events
        • Stream View
        • Zero Heap
    • Avalanche.Message
      • Introduction
      • IMessage
      • IMessageProvider
      • IMessageDescription
      • IMessageDescriptions
      • MessageDescriptionsBase
      • MessageLevel
      • Message printing
      • Messages and Exceptions
      • Microsoft.Extensions
        • DependencyInjection
      • Articles
        • Aggregate Messages
        • Localization
        • Localization Templates
        • Logging
        • Validation
    • Avalanche.Module
      • Introduction
      • IServiceCollection
      • IServiceBuilder
      • IHostBuilder
      • Dependencies
    • Avalanche.Options
      • Introduction
      • OptionsExtensions
      • OptionsMonitorCast
    • Avalanche.Protobuf
      • Introduction
    • Avalanche.Service
      • Introduction
      • Service
        • Introduction
        • IService
        • IServiceDisposable
        • IServiceDecoration
        • IServiceCast
        • IServiceObservable
        • IServiceContainer
        • Construction
        • Query
        • CancellationToken
        • CachePolicy
        • Scope
      • Handler
        • Introduction
        • IHandler
        • IHandlerCast
        • IHandlerDecoration
        • IHandlerWithOrder
        • CancellationToken
        • Cyclicity
        • Delegates
        • Invokable
        • ExportAttribute
        • OrderAttribute
        • PrintTree
        • Recursion
      • Query
        • Introduction
        • IQuery
        • IQueryCast
        • IQueryDecoration
      • Entry
        • Introduction
        • IEntry
        • IEntryCast
        • IEntryDecoration
        • IEntryObservable
        • IEntryVisitable
        • EntryState
      • Request
        • Introduction
        • IRequest
        • IRequestFor
        • IRequestToBeCached
        • IRequestToBeDisposed
        • RequestAttribute
        • ContextParameterAttribute
        • Print Tree
      • Dependency Injection
        • Introduction
        • Asp.Net
        • ServiceRequest<T>
        • Decorating a service
        • Handler
        • CachePolicy
        • CancellationToken
        • QueryLogger
        • IHostBuilder
      • Examples
        • NodeCount
        • Expression
        • Mapper
      • Articles
        • Benchmarks
        • Error Handling
        • ServiceMessages
    • Avalanche.StatusCode
      • Introduction
      • HResult
        • Introduction
        • HResult.Facilities
        • BasicMessages
        • RpcMessages
        • DispatchMessages
        • StorageMessages
        • ItfMessages
        • Win32Messages
        • WindowsMessages
        • SspiMessages
        • CertMessages
        • MediaServerMessages
        • SetupApiMessages
        • ScardMessages
        • ComPlusMessages
        • ClrMessages
        • UserModeFilterManagerMessages
        • GraphicsMessages
        • TpmServicesMessages
        • TpmSoftwareMessages
        • PlaMessages
        • FveMessages
        • FwpMessages
        • NdisMessages
        • DltMessages
      • System
        • Introduction
        • AccessControlMessages
        • AggregateMessages
        • AppDomainMessages
        • ArgumentMessages
        • ArgumentNullMessages
        • ArgumentOutOfRangeMessages
        • ArithmeticMessages
        • ArrayMessages
        • AssemblyMessages
        • BadImageFormatMessages
        • CodeContractMessages
        • CodePageMessages
        • CollectionsMessages
        • CompilerServiceMessages
        • CryptographyMessages
        • CultureMessages
        • DiagnosticsMessages
        • EventSourceMessages
        • ExecutionEngineMessages
        • FormatMessages
        • HostProtectionMessages
        • IOMessages
        • IndexOutOfRangeMessages
        • InteropServiceMessages
        • InvalidCastMessages
        • InvalidOperationMessages
        • IsolatedStorageMessages
        • LazyMessages
        • MarshalerMessages
        • MemoryMessages
        • MiscellaneousMessages
        • NotImplementedMessages
        • NotSupportedMessages
        • ObjectDisposedMessages
        • OperationCanceledMessages
        • OverflowMessages
        • PlatformMessages
        • PolicyMessages
        • PrincipalMessages
        • ProgramMessages
        • ReferenceMessages
        • ReflectionMessages
        • RegionMessages
        • RemotingMessages
        • ResourcesMessages
        • SecurityMessages
        • SerializationMessages
        • StackMessages
        • TaskMessages
        • TextMessages
        • ThreadingMessages
        • TimeZoneMessages
        • TypeMessages
        • XmlMessages
      • HttpStatusCode
      • OpcUaStatusCode
    • Avalanche.Template
      • Introduction
      • TemplateFormats
      • ITemplatePrintable
      • ITemplateFormatPrintable
      • ITemplateText
      • ITemplateBreakdown
      • ITemplateFormat
      • ITemplateFormats
      • Extract Arguments
      • Emplacement
    • Avalanche.Tokenizer
      • Introduction
      • IToken
      • ITokenizer
      • Tokenizers
    • Avalanche.Utilities
      • Introduction
      • Collections
        • ArrayList
        • ArrayUtilities
        • BijectionMap
        • CachedSelect
        • DictionaryAdapter
        • EnumerableExtensions
        • LocakableDictionary
        • LockableList
        • MapList
        • Pipe
        • RingQueue
        • StructList
        • Tuples
        • TupleUtilities
      • Comparers
        • IGraphComparer
        • IGraphComparable
        • AlphaNumericComparer
        • EnumerableComparer
        • EnumerableGraphComparer
        • ReferenceComparer
        • KeyValuePairComparer
        • DefaultComparerProvider
        • RecordComparer
      • Cloners
        • ICloner
        • IGraphCloner
        • IGraphCloneable
        • ListCloner
        • DictionaryCloner
        • FieldCloner
        • PassthroughCloner
        • RecordCloner
        • ClonerProvider
      • Dispose
        • IDisposeAttachable
        • IDisposeBelatable
      • Provider
        • Introduction
        • ProviderBase
        • Delegate
        • Pipe
        • Cache
        • ResultCapture
        • AsReadOnly
        • SharedResource
        • AsService
        • IProviderEvent
      • Record
        • IRecordDescription
        • IFieldDescription
        • IConstructorDescription
        • IConstructionDescription
        • IParameterDescription
        • IRecordProviders
        • RecordDelegates
          • RecordCreate
          • RecordClone
          • RecordCopy
          • IRecordDelegates
        • FieldDelegates
          • FieldRead
          • FieldWrite
          • RecreateWith
          • IFieldDelegates
      • Reflection
        • EnumDescription
      • String
        • IEscaper
        • UnicodeString
        • Hex
      • Miscellaneous
        • IIdGenerator
        • Permutation
        • IReadOnly
        • IUserDataContainer
        • ITreeNode
        • Void
    • Avalanche.Writer
      • Introduction
      • ConstantWriter
      • Context
      • ConvertWriter
      • DefaultConstructor
      • DelegateWriter
      • PassthroughWriter
      • Referer
      • TypeCast
      • Writer
      • WriterPipe
      • WriterMessages

    Cyclicity

    An object graph that has circular chain of references is cyclical.

    Cyclic query

    Service library can process cyclic request object graphs and produce cyclic response graphs. It breaks down complicated cyclic requests into format that is processable by simple handlers.

    However, there are a few considerations that handler implementations must adhere to:

    1. Result objects must be referable types.
    2. Result objects must be constructed in two phases. Initial reference must be assigned right after construction.
    3. Cache must be used for the request type.
    4. Other handlers must get initial reference with .GetInitialized().
    5. One handler must set the entry into Completed state e.g. with .SetValue().

    Handler cycle may occur if rules are not followed. The most common cause is that request is non-cached.

    Handlers must be carefully designed to avoid cycles, as the library does not always detect cyclic processing loop. Loop detection is kept simple to to conserve CPU and memory cycles in other cases.

    Example

    In the following example node count of an object graph is calculated. This example uses so called dynamic programming and utilizes cached results.

    Node graph

    // Create service
    IService service = Services.Create(new NodeHandler());
    // Get count service
    IService<NodeCount, int> countService = service.Cast<NodeCount, int>();
    
    // Create nodes
    Node root = new("root"), a = new("a"), b = new("b"), c = new("c");
    // Cyclic graph: root->a, a->b, b->c, c->a
    root.Edges.Add(a); a.Edges.Add(b); b.Edges.Add(c); c.Edges.Add(a);
    
    // Count number of nodes for each start node
    WriteLine(countService.GetRequired(root)); // "4"
    WriteLine(countService.GetRequired(a)); // "3"
    WriteLine(countService.GetRequired(b)); // "3"
    WriteLine(countService.GetRequired(c)); // "3"
    

    Node class has name and forward edges.

    public record Node(string name)
    {
        /// <summary>Forward reference edge</summary>
        public List<Node> Edges = new();
    }
    

    NodeCount is request to calculate node count.

    /// <summary>Request to calculate node count in <paramref name="node"/>.</summary>
    public record struct NodeCount(Node node) : IRequestFor<int>, IRequestToBeCached
    {
        public static implicit operator NodeCount(Node node) => new NodeCount(node);
    }
    

    NodeTraverse is request to traverse graph on forward edges.

    /// <summary>Request to traverse forward edges</summary>
    public record struct NodeTraverse(Node node) : IRequestFor<HashSet<Node>>, IRequestToBeCached
    {
        public static implicit operator NodeTraverse(Node node) => new NodeTraverse(node);
    }
    

    NodeHandler processes node requests

    /// <summary>Calculates nodes</summary>
    public class NodeHandler : IHandler<NodeCount, int>, IHandler<NodeTraverse, HashSet<Node>>
    {
        /// <summary>Count nodes</summary>
        public void Handle(IQuery<NodeCount, int> query)
        {
            // Already handled
            if (query.Handled()) return;
            // Traverse node graph
            HashSet<Node> traverse = query.Service.GetRequired<NodeTraverse, HashSet<Node>>(query.Request.node, query.CancellationToken, query.Context);
            // Assign count
            query.Response.SetValue(traverse.Count);
        }
    
        /// <summary>Traverse node graph</summary>
        public void Handle(IQuery<NodeTraverse, HashSet<Node>> query)
        {
            // Already handled
            if (query.Handled()) return;
            // Get node
            Node node = query.Request.node;
            // Create traverse set
            HashSet<Node> traverse = new(node.Edges.Count + 1);
            // Add self and direct forward edges
            traverse.Add(node);
            traverse.AddRange(node.Edges);
            // Assign initial value (required)
            query.Response.SetValue(traverse);
            // Remember the count that was in the initial assignment
            int initialCount = traverse.Count;
            // Add children
            foreach (Node child in node.Edges)
            {
                // Get entry to initial value, same as service.GetInitialized(), but returns entry instead
                IEntry<HashSet<Node>> entry = ((IEntryProvider)query.Service).GetEntry<NodeTraverse, HashSet<Node>>(child, query.CancellationToken, query.Context)!;
                // Add initial values
                traverse.AddRange(entry.Value<HashSet<Node>>()!);
                // Add state change listener
                ((IEntryObservable)entry).Subscribe(@event =>
                {
                    // Process on Value update, but not on dispose
                    if ((@event.NewState.Status & (EntryStatus.Value | EntryStatus.ValueDisposed | EntryStatus.Disposed)) != EntryStatus.Value) return; 
                    // Get nodes
                    HashSet<Node>? nodes = @event.NewState.Value as HashSet<Node>;
                    // No nodes
                    if (nodes == null) return;
                    // Get count
                    int prevCount = traverse.Count;
                    // Add nodes from listened object
                    traverse.AddRange(nodes);
                    // No change
                    if (traverse.Count == prevCount) return;
                    // Invoke listeners of change in result
                    query.Response.SetValue(traverse);
                });
            }
            // Invoke listeners of change in result
            if (traverse.Count != initialCount) query.Response.SetValue(traverse);
        }
    }
    

    If intention is not to keep the cache, then a transient scope is needed.

    // Create service
    IService service = Services.Create((ServiceHandlers.Instance, ServiceScopeHandler.Instance, new NodeHandler()));
    
    // Create nodes
    Node root = new("root"), a = new("a"), b = new("b"), c = new("c");
    // Cyclic graph: root->a, a->b, b->c, c->a
    root.Edges.Add(a); a.Edges.Add(b); b.Edges.Add(c); c.Edges.Add(a);
    
    // Create transient scope
    using IServiceScope scope = ((IServiceScopeFactory)service.GetService(typeof(IServiceScopeFactory))!).CreateScope();
    // Get transient scoped count service
    IService<NodeCount, int> countService = ((IService)scope.ServiceProvider).Cast<NodeCount, int>();
    // Count number of nodes for each start node
    WriteLine(countService.GetRequired(root)); // "4"
    WriteLine(countService.GetRequired(a)); // "3"
    WriteLine(countService.GetRequired(b)); // "3"
    WriteLine(countService.GetRequired(c)); // "3"
    scope.Dispose();
    

    Full Example

    Full example
    using System.Collections.Generic;
    using Avalanche.Service;
    using Avalanche.Utilities;
    using Microsoft.Extensions.DependencyInjection;
    using static System.Console;
    
    public class handler_cyclic
    {
        public static void Run()
        {
            {
                // <01>
                // Create service
                IService service = Services.Create(new NodeHandler());
                // Get count service
                IService<NodeCount, int> countService = service.Cast<NodeCount, int>();
    
                // Create nodes
                Node root = new("root"), a = new("a"), b = new("b"), c = new("c");
                // Cyclic graph: root->a, a->b, b->c, c->a
                root.Edges.Add(a); a.Edges.Add(b); b.Edges.Add(c); c.Edges.Add(a);
    
                // Count number of nodes for each start node
                WriteLine(countService.GetRequired(root)); // "4"
                WriteLine(countService.GetRequired(a)); // "3"
                WriteLine(countService.GetRequired(b)); // "3"
                WriteLine(countService.GetRequired(c)); // "3"
                // </01>
            }
            {
                // <02>
                // Create service
                IService service = Services.Create((ServiceHandlers.Instance, ServiceScopeHandler.Instance, new NodeHandler()));
    
                // Create nodes
                Node root = new("root"), a = new("a"), b = new("b"), c = new("c");
                // Cyclic graph: root->a, a->b, b->c, c->a
                root.Edges.Add(a); a.Edges.Add(b); b.Edges.Add(c); c.Edges.Add(a);
    
                // Create transient scope
                using IServiceScope scope = ((IServiceScopeFactory)service.GetService(typeof(IServiceScopeFactory))!).CreateScope();
                // Get transient scoped count service
                IService<NodeCount, int> countService = ((IService)scope.ServiceProvider).Cast<NodeCount, int>();
                // Count number of nodes for each start node
                WriteLine(countService.GetRequired(root)); // "4"
                WriteLine(countService.GetRequired(a)); // "3"
                WriteLine(countService.GetRequired(b)); // "3"
                WriteLine(countService.GetRequired(c)); // "3"
                scope.Dispose();
                // </02>
            }
        }
    
        // <91>
        public record Node(string name)
        {
            /// <summary>Forward reference edge</summary>
            public List<Node> Edges = new();
        }
        // </91>
    
        // <92>
        /// <summary>Request to calculate node count in <paramref name="node"/>.</summary>
        public record struct NodeCount(Node node) : IRequestFor<int>, IRequestToBeCached
        {
            public static implicit operator NodeCount(Node node) => new NodeCount(node);
        }
        // </92>
    
        // <93>
        /// <summary>Request to traverse forward edges</summary>
        public record struct NodeTraverse(Node node) : IRequestFor<HashSet<Node>>, IRequestToBeCached
        {
            public static implicit operator NodeTraverse(Node node) => new NodeTraverse(node);
        }
        // </93>
    
        // <94>
        /// <summary>Calculates nodes</summary>
        public class NodeHandler : IHandler<NodeCount, int>, IHandler<NodeTraverse, HashSet<Node>>
        {
            /// <summary>Count nodes</summary>
            public void Handle(IQuery<NodeCount, int> query)
            {
                // Already handled
                if (query.Handled()) return;
                // Traverse node graph
                HashSet<Node> traverse = query.Service.GetRequired<NodeTraverse, HashSet<Node>>(query.Request.node, query.CancellationToken, query.Context);
                // Assign count
                query.Response.SetValue(traverse.Count);
            }
    
            /// <summary>Traverse node graph</summary>
            public void Handle(IQuery<NodeTraverse, HashSet<Node>> query)
            {
                // Already handled
                if (query.Handled()) return;
                // Get node
                Node node = query.Request.node;
                // Create traverse set
                HashSet<Node> traverse = new(node.Edges.Count + 1);
                // Add self and direct forward edges
                traverse.Add(node);
                traverse.AddRange(node.Edges);
                // Assign initial value (required)
                query.Response.SetValue(traverse);
                // Remember the count that was in the initial assignment
                int initialCount = traverse.Count;
                // Add children
                foreach (Node child in node.Edges)
                {
                    // Get entry to initial value, same as service.GetInitialized(), but returns entry instead
                    IEntry<HashSet<Node>> entry = ((IEntryProvider)query.Service).GetEntry<NodeTraverse, HashSet<Node>>(child, query.CancellationToken, query.Context)!;
                    // Add initial values
                    traverse.AddRange(entry.Value<HashSet<Node>>()!);
                    // Add state change listener
                    ((IEntryObservable)entry).Subscribe(@event =>
                    {
                        // Process on Value update, but not on dispose
                        if ((@event.NewState.Status & (EntryStatus.Value | EntryStatus.ValueDisposed | EntryStatus.Disposed)) != EntryStatus.Value) return; 
                        // Get nodes
                        HashSet<Node>? nodes = @event.NewState.Value as HashSet<Node>;
                        // No nodes
                        if (nodes == null) return;
                        // Get count
                        int prevCount = traverse.Count;
                        // Add nodes from listened object
                        traverse.AddRange(nodes);
                        // No change
                        if (traverse.Count == prevCount) return;
                        // Invoke listeners of change in result
                        query.Response.SetValue(traverse);
                    });
                }
                // Invoke listeners of change in result
                if (traverse.Count != initialCount) query.Response.SetValue(traverse);
            }
        }
        // </94>
    
    }
    
    In This Article
    Back to top