From 6175aa21ad17abd7933da1420436d87541d1c672 Mon Sep 17 00:00:00 2001 From: Jasper Date: Thu, 12 Feb 2026 10:06:35 +0100 Subject: [PATCH 1/6] Implemented initial enum and assosiated logic. --- ...htTransitionPipelineConfigurator.Switch.cs | 12 +++++-- ...ightTransitionPipelineConfigurator.When.cs | 10 ++++-- .../ReactiveNode/CompositeAddBehavior.cs | 14 ++++++++ ...htTransitionReactiveNodeConfigurator.On.cs | 32 +++++++++++++------ ...htTransitionReactiveNodeConfigurator.On.cs | 6 ++-- ...htTransitionReactiveNodeConfigurator.On.cs | 4 +-- 6 files changed, 59 insertions(+), 19 deletions(-) create mode 100644 src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeAddBehavior.cs diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs index ec189fb..74b9b19 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs @@ -120,7 +120,10 @@ public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch AddReactiveNodeSwitch(IObservable observable, Action> trueConfigure, Action> falseConfigure) { - // Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. + /* + * Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. + * This currently doesn't matter, but it seems like good practice to keep the context consistent for all nodes created within the configure action, and it also allows for more flexible future implementations of the composite pattern. + */ return AddReactiveNode(c => c .On(observable.Where(x => x), trueConfigure) .On(observable.Where(x => !x), falseConfigure)); @@ -142,10 +145,13 @@ public ILightTransitionPipelineConfigurator AddPipelineSwitch AddPipelineSwitch(IObservable observable, Action> trueConfigure, Action> falseConfigure) { - // Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. + /* + * Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. + * This currently doesn't matter, but it seems like good practice to keep the context consistent for all nodes created within the configure action, and it also allows for more flexible future implementations of the composite pattern. + */ return AddReactiveNode(c => c .On(observable.Where(x => x), trueConfigure) - .On(observable.Where(x => x), falseConfigure)); + .On(observable.Where(x => !x), falseConfigure)); } /// diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs index 9e44157..d35d9a9 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs @@ -121,7 +121,10 @@ public ILightTransitionPipelineConfigurator AddReactiveNodeWhen public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservable observable, Action> configure) { - // Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. + /* + * Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. + * This currently doesn't matter, but it seems like good practice to keep the context consistent for all nodes created within the configure action, and it also allows for more flexible future implementations of the composite pattern. + */ return AddReactiveNode(c => c .On(observable.Where(x => x), configure) .PassThroughOn(observable.Where(x => !x))); @@ -142,7 +145,10 @@ public ILightTransitionPipelineConfigurator AddPipelineWhen /// public ILightTransitionPipelineConfigurator AddPipelineWhen(IObservable observable, Action> configure) { - // Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. + /* + * Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. + * This currently doesn't matter, but it seems like good practice to keep the context consistent for all nodes created within the configure action, and it also allows for more flexible future implementations of the composite pattern. + */ return AddReactiveNode(c => c .On(observable.Where(x => x), configure) .PassThroughOn(observable.Where(x => !x))); diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeAddBehavior.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeAddBehavior.cs new file mode 100644 index 0000000..7e0b552 --- /dev/null +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeAddBehavior.cs @@ -0,0 +1,14 @@ +namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; + +public enum CompositeAddBehavior +{ + /// + /// Gets or sets a value indicating whether instances should be created immediately when registered in a composite + /// context. + /// + /// When set to , objects registered in a composite context are + /// instantiated as soon as they are added, rather than being created when the corresponding observable emits true. + /// This way, configuration can be applied to the composite context rather than the context of the individual nodes. + ImmediatelyInstantiateInCompositeContext, + InstantiateInChildContainer, +} \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs index 6112066..c2a9f58 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs @@ -40,22 +40,34 @@ public ILightTransitionReactiveNodeConfigurator On(IObservable tri } /// - public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> pipelineConfigurator) + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> pipelineConfigurator, CompositeAddBehavior addBehavior = CompositeAddBehavior.ImmediatelyInstantiateInCompositeContext) { - // Note: we create the pipeline in composite context so all configuration is also applied in that context. - var pipelines = lightPipelineFactory.CreateLightPipelines(configurators.Values.Select(c => c.Light), - pipelineConfigurator); - configurators.Values.ForEach(c => c.On(triggerObservable, _ => pipelines[c.Light.Id])); + if (addBehavior == CompositeAddBehavior.ImmediatelyInstantiateInCompositeContext) + { + // Note: we create the pipeline in composite context so all configuration is also applied in that context. + var pipelines = lightPipelineFactory.CreateLightPipelines(configurators.Values.Select(c => c.Light), + pipelineConfigurator); + configurators.Values.ForEach(c => c.On(triggerObservable, _ => pipelines[c.Light.Id])); + return this; + } + + configurators.Values.ForEach(c => c.On(triggerObservable, pipelineConfigurator)); return this; } /// - public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> configure) + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> configure, CompositeAddBehavior addBehavior = CompositeAddBehavior.ImmediatelyInstantiateInCompositeContext) { - // Note: we create the pipeline in composite context so all configuration is also applied in that context. - var nodes = reactiveNodeFactory.CreateReactiveNodes(configurators.Values.Select(c => c.Light), - configure); - configurators.Values.ForEach(c => c.On(triggerObservable, _ => nodes[c.Light.Id])); + if (addBehavior == CompositeAddBehavior.ImmediatelyInstantiateInCompositeContext) + { + // Note: we create the pipeline in composite context so all configuration is also applied in that context. + var nodes = reactiveNodeFactory.CreateReactiveNodes(configurators.Values.Select(c => c.Light), + configure); + configurators.Values.ForEach(c => c.On(triggerObservable, _ => nodes[c.Light.Id])); + return this; + } + + configurators.Values.ForEach(c => c.On(triggerObservable, configure)); return this; } diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs index 8de78c5..9e33092 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs @@ -74,7 +74,8 @@ ILightTransitionReactiveNodeConfigurator On(IObservable triggerObs /// An action to configure the nested pipeline. /// The configurator instance for method chaining. ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, - Action> pipelineConfigurator); + Action> pipelineConfigurator, + CompositeAddBehavior addBehavior = CompositeAddBehavior.ImmediatelyInstantiateInCompositeContext); /// /// Registers a trigger that activates a nested reactive node configured by when the emits a value. @@ -84,7 +85,8 @@ ILightTransitionReactiveNodeConfigurator On(IObservable triggerObs /// An action to configure the nested reactive node. /// The configurator instance for method chaining. ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, - Action> configure); + Action> configure, + CompositeAddBehavior addBehavior = CompositeAddBehavior.ImmediatelyInstantiateInCompositeContext); /// /// Registers a pass-through trigger that allows the current input to pass through unchanged when the emits a value. diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs index 8dcc621..f6bd1d8 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs @@ -41,10 +41,10 @@ public ILightTransitionReactiveNodeConfigurator On(IObservable tri AddNodeSource(triggerObservable.Select(_ => nodeFactory)); /// - public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> pipelineConfigurator) => On(triggerObservable, s => s.GetRequiredService().CreateLightPipeline(Light, pipelineConfigurator)); + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> pipelineConfigurator, CompositeAddBehavior _ = CompositeAddBehavior.ImmediatelyInstantiateInCompositeContext) => On(triggerObservable, s => s.GetRequiredService().CreateLightPipeline(Light, pipelineConfigurator)); /// - public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> configure) => On(triggerObservable, s => s.GetRequiredService().CreateReactiveNode(Light, configure)); + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> configure, CompositeAddBehavior _ = CompositeAddBehavior.ImmediatelyInstantiateInCompositeContext) => On(triggerObservable, s => s.GetRequiredService().CreateReactiveNode(Light, configure)); /// public ILightTransitionReactiveNodeConfigurator PassThroughOn(IObservable triggerObservable) From c9594df6811a6bcc23f1561e3ede6b0f0ee9517b Mon Sep 17 00:00:00 2001 From: Jasper Date: Thu, 12 Feb 2026 10:10:28 +0100 Subject: [PATCH 2/6] Rename enum --- ...ositeAddBehavior.cs => CompositeInstantiationScope.cs} | 6 +++--- ...CompositeLightTransitionReactiveNodeConfigurator.On.cs | 8 ++++---- .../ILightTransitionReactiveNodeConfigurator.On.cs | 4 ++-- .../LightTransitionReactiveNodeConfigurator.On.cs | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) rename src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/{CompositeAddBehavior.cs => CompositeInstantiationScope.cs} (84%) diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeAddBehavior.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeInstantiationScope.cs similarity index 84% rename from src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeAddBehavior.cs rename to src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeInstantiationScope.cs index 7e0b552..001d50c 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeAddBehavior.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeInstantiationScope.cs @@ -1,6 +1,6 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; -public enum CompositeAddBehavior +public enum CompositeInstantiationScope { /// /// Gets or sets a value indicating whether instances should be created immediately when registered in a composite @@ -9,6 +9,6 @@ public enum CompositeAddBehavior /// When set to , objects registered in a composite context are /// instantiated as soon as they are added, rather than being created when the corresponding observable emits true. /// This way, configuration can be applied to the composite context rather than the context of the individual nodes. - ImmediatelyInstantiateInCompositeContext, - InstantiateInChildContainer, + Shared, + PerChild, } \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs index c2a9f58..f50b179 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs @@ -40,9 +40,9 @@ public ILightTransitionReactiveNodeConfigurator On(IObservable tri } /// - public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> pipelineConfigurator, CompositeAddBehavior addBehavior = CompositeAddBehavior.ImmediatelyInstantiateInCompositeContext) + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> pipelineConfigurator, CompositeInstantiationScope instantiationScope = CompositeInstantiationScope.Shared) { - if (addBehavior == CompositeAddBehavior.ImmediatelyInstantiateInCompositeContext) + if (instantiationScope == CompositeInstantiationScope.Shared) { // Note: we create the pipeline in composite context so all configuration is also applied in that context. var pipelines = lightPipelineFactory.CreateLightPipelines(configurators.Values.Select(c => c.Light), @@ -56,9 +56,9 @@ public ILightTransitionReactiveNodeConfigurator On(IObservable tri } /// - public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> configure, CompositeAddBehavior addBehavior = CompositeAddBehavior.ImmediatelyInstantiateInCompositeContext) + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> configure, CompositeInstantiationScope instantiationScope = CompositeInstantiationScope.Shared) { - if (addBehavior == CompositeAddBehavior.ImmediatelyInstantiateInCompositeContext) + if (instantiationScope == CompositeInstantiationScope.Shared) { // Note: we create the pipeline in composite context so all configuration is also applied in that context. var nodes = reactiveNodeFactory.CreateReactiveNodes(configurators.Values.Select(c => c.Light), diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs index 9e33092..81595e4 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs @@ -75,7 +75,7 @@ ILightTransitionReactiveNodeConfigurator On(IObservable triggerObs /// The configurator instance for method chaining. ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> pipelineConfigurator, - CompositeAddBehavior addBehavior = CompositeAddBehavior.ImmediatelyInstantiateInCompositeContext); + CompositeInstantiationScope instantiationScope = CompositeInstantiationScope.Shared); /// /// Registers a trigger that activates a nested reactive node configured by when the emits a value. @@ -86,7 +86,7 @@ ILightTransitionReactiveNodeConfigurator On(IObservable triggerObs /// The configurator instance for method chaining. ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> configure, - CompositeAddBehavior addBehavior = CompositeAddBehavior.ImmediatelyInstantiateInCompositeContext); + CompositeInstantiationScope instantiationScope = CompositeInstantiationScope.Shared); /// /// Registers a pass-through trigger that allows the current input to pass through unchanged when the emits a value. diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs index f6bd1d8..009d661 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs @@ -41,10 +41,10 @@ public ILightTransitionReactiveNodeConfigurator On(IObservable tri AddNodeSource(triggerObservable.Select(_ => nodeFactory)); /// - public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> pipelineConfigurator, CompositeAddBehavior _ = CompositeAddBehavior.ImmediatelyInstantiateInCompositeContext) => On(triggerObservable, s => s.GetRequiredService().CreateLightPipeline(Light, pipelineConfigurator)); + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> pipelineConfigurator, CompositeInstantiationScope _ = CompositeInstantiationScope.Shared) => On(triggerObservable, s => s.GetRequiredService().CreateLightPipeline(Light, pipelineConfigurator)); /// - public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> configure, CompositeAddBehavior _ = CompositeAddBehavior.ImmediatelyInstantiateInCompositeContext) => On(triggerObservable, s => s.GetRequiredService().CreateReactiveNode(Light, configure)); + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> configure, CompositeInstantiationScope _ = CompositeInstantiationScope.Shared) => On(triggerObservable, s => s.GetRequiredService().CreateReactiveNode(Light, configure)); /// public ILightTransitionReactiveNodeConfigurator PassThroughOn(IObservable triggerObservable) From 5a9f61fc272e0b924c2aa79512a3b218b8b1e7b6 Mon Sep 17 00:00:00 2001 From: Jasper Date: Thu, 12 Feb 2026 10:19:26 +0100 Subject: [PATCH 3/6] Updating comments. --- .../CompositeInstantiationScope.cs | 25 +++++++++++++++---- ...htTransitionReactiveNodeConfigurator.On.cs | 10 ++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeInstantiationScope.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeInstantiationScope.cs index 001d50c..804dc4b 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeInstantiationScope.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeInstantiationScope.cs @@ -1,14 +1,29 @@ namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; +/// +/// Specifies the service provider scope in which composite child objects (pipelines, reactive nodes) are instantiated. +/// public enum CompositeInstantiationScope { /// - /// Gets or sets a value indicating whether instances should be created immediately when registered in a composite - /// context. + /// Objects are instantiated immediately in the shared composite context. /// - /// When set to , objects registered in a composite context are - /// instantiated as soon as they are added, rather than being created when the corresponding observable emits true. - /// This way, configuration can be applied to the composite context rather than the context of the individual nodes. + /// + /// When using this scope, objects are created as soon as they are registered, allowing configuration + /// to be applied uniformly across all composite members. The nested pipeline or node shares the same + /// lifetime as the parent pipeline—it is created once and remains active until the parent is disposed. + /// This is useful when you want shared state or behavior across all lights in a composite group. + /// Shared, + + /// + /// Objects are instantiated in individual child containers, each with its own light-specific service scope. + /// + /// + /// When using this scope, a new instance is created each time the corresponding observable emits, + /// and each light receives its own instance within its own service scope. The instance is disposed + /// whenever it is replaced by a new node (e.g., when another trigger fires). This is useful when you + /// need light-specific dependencies, isolated state per light, or fresh instances on each activation. + /// PerChild, } \ No newline at end of file diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs index 81595e4..6a9b74c 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs @@ -72,6 +72,11 @@ ILightTransitionReactiveNodeConfigurator On(IObservable triggerObs /// The type of values emitted by the trigger observable. /// The observable that triggers the pipeline activation. /// An action to configure the nested pipeline. + /// + /// Specifies where the nested pipeline is instantiated. Use + /// to create pipelines immediately in the composite context (shared across all lights), or + /// to defer creation to individual child service scopes. + /// /// The configurator instance for method chaining. ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> pipelineConfigurator, @@ -83,6 +88,11 @@ ILightTransitionReactiveNodeConfigurator On(IObservable triggerObs /// The type of values emitted by the trigger observable. /// The observable that triggers the reactive node activation. /// An action to configure the nested reactive node. + /// + /// Specifies where the nested reactive node is instantiated. Use + /// to create nodes immediately in the composite context (shared across all lights), or + /// to defer creation to individual child service scopes. + /// /// The configurator instance for method chaining. ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> configure, From 91a2bb7b3f57282f40d1cdb8e54baae1faf4fadc Mon Sep 17 00:00:00 2001 From: Jasper Date: Thu, 12 Feb 2026 10:58:24 +0100 Subject: [PATCH 4/6] Applied InstantiationScope to ILightTransitionPipeline as well. --- ...htTransitionPipelineConfigurator.Switch.cs | 59 ++++++++----------- ...ightTransitionPipelineConfigurator.When.cs | 25 ++------ ...htTransitionPipelineConfigurator.Switch.cs | 12 ++-- ...ightTransitionPipelineConfigurator.When.cs | 9 ++- ...htTransitionPipelineConfigurator.Switch.cs | 26 ++++---- ...ightTransitionPipelineConfigurator.When.cs | 25 ++++++-- ...htTransitionReactiveNodeConfigurator.On.cs | 8 +-- ...htTransitionReactiveNodeConfigurator.On.cs | 12 ++-- ...ntiationScope.cs => InstantiationScope.cs} | 2 +- ...htTransitionReactiveNodeConfigurator.On.cs | 28 +++++++-- 10 files changed, 112 insertions(+), 94 deletions(-) rename src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/{CompositeInstantiationScope.cs => InstantiationScope.cs} (97%) diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs index 74b9b19..525e05b 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.Switch.cs @@ -1,10 +1,9 @@ using CodeCasa.AutomationPipelines.Lights.Extensions; using CodeCasa.AutomationPipelines.Lights.ReactiveNode; +using CodeCasa.Lights; using Microsoft.Extensions.DependencyInjection; - - +using System; using System.Reactive.Linq; -using CodeCasa.Lights; namespace CodeCasa.AutomationPipelines.Lights.Pipeline; @@ -105,53 +104,45 @@ public ILightTransitionPipelineConfigurator Switch public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(Action> trueConfigure, - Action> falseConfigure) where TObservable : IObservable - { - /* - * For this implementation we can either instantiate the TObservable for each container and pass configure to them individual, breaking composite dimming behavior. - * Or we can create a single TObservable without light context. - * I decided to go with the latter to preserve composite dimming behavior. - */ - var observable = ActivatorUtilities.CreateInstance(serviceProvider); - return AddReactiveNodeSwitch(observable, trueConfigure, falseConfigure); + Action> falseConfigure, InstantiationScope instantiationScope = InstantiationScope.Shared) where TObservable : IObservable + { + if (instantiationScope == InstantiationScope.Shared) + { + var observable = ActivatorUtilities.CreateInstance(serviceProvider); + return AddReactiveNodeSwitch(observable, trueConfigure, falseConfigure); + } + NodeContainers.Values.ForEach(b => b.AddReactiveNodeSwitch(trueConfigure, falseConfigure, instantiationScope)); + return this; } /// public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(IObservable observable, Action> trueConfigure, - Action> falseConfigure) + Action> falseConfigure, InstantiationScope instantiationScope = InstantiationScope.Shared) { - /* - * Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. - * This currently doesn't matter, but it seems like good practice to keep the context consistent for all nodes created within the configure action, and it also allows for more flexible future implementations of the composite pattern. - */ return AddReactiveNode(c => c - .On(observable.Where(x => x), trueConfigure) - .On(observable.Where(x => !x), falseConfigure)); + .On(observable.Where(x => x), trueConfigure, instantiationScope) + .On(observable.Where(x => !x), falseConfigure, instantiationScope)); } /// - public ILightTransitionPipelineConfigurator AddPipelineSwitch(Action> trueConfigure, Action> falseConfigure) where TObservable : IObservable + public ILightTransitionPipelineConfigurator AddPipelineSwitch(Action> trueConfigure, Action> falseConfigure, InstantiationScope instantiationScope = InstantiationScope.Shared) where TObservable : IObservable { - /* - * For this implementation we can either instantiate the TObservable for each container and pass configure to them individual, breaking composite dimming behavior. - * Or we can create a single TObservable without light context. - * I decided to go with the latter to preserve composite dimming behavior. - */ - var observable = ActivatorUtilities.CreateInstance(serviceProvider); - return AddPipelineSwitch(observable, trueConfigure, falseConfigure); + if (instantiationScope == InstantiationScope.Shared) + { + var observable = ActivatorUtilities.CreateInstance(serviceProvider); + return AddPipelineSwitch(observable, trueConfigure, falseConfigure); + } + NodeContainers.Values.ForEach(b => b.AddPipelineSwitch(trueConfigure, falseConfigure, instantiationScope)); + return this; } /// public ILightTransitionPipelineConfigurator AddPipelineSwitch(IObservable observable, Action> trueConfigure, - Action> falseConfigure) + Action> falseConfigure, InstantiationScope instantiationScope = InstantiationScope.Shared) { - /* - * Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. - * This currently doesn't matter, but it seems like good practice to keep the context consistent for all nodes created within the configure action, and it also allows for more flexible future implementations of the composite pattern. - */ return AddReactiveNode(c => c - .On(observable.Where(x => x), trueConfigure) - .On(observable.Where(x => !x), falseConfigure)); + .On(observable.Where(x => x), trueConfigure, instantiationScope) + .On(observable.Where(x => !x), falseConfigure, instantiationScope)); } /// diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs index d35d9a9..1d1454f 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs @@ -119,38 +119,25 @@ public ILightTransitionPipelineConfigurator AddReactiveNodeWhen - public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservable observable, Action> configure) + public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservable observable, Action> configure, InstantiationScope instantiationScope = InstantiationScope.Shared) { - /* - * Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. - * This currently doesn't matter, but it seems like good practice to keep the context consistent for all nodes created within the configure action, and it also allows for more flexible future implementations of the composite pattern. - */ return AddReactiveNode(c => c - .On(observable.Where(x => x), configure) + .On(observable.Where(x => x), configure, instantiationScope) .PassThroughOn(observable.Where(x => !x))); } /// - public ILightTransitionPipelineConfigurator AddPipelineWhen(Action> configure) where TObservable : IObservable + public ILightTransitionPipelineConfigurator AddPipelineWhen(Action> configure, InstantiationScope instantiationScope = InstantiationScope.Shared) where TObservable : IObservable { - /* - * For this implementation we can either instantiate the TObservable for each container and pass configure to them individual, breaking composite dimming behavior. - * Or we can create a single TObservable without light context. - * I decided to go with the latter to preserve composite dimming behavior. - */ var observable = ActivatorUtilities.CreateInstance(serviceProvider); - return AddPipelineWhen(observable, configure); + return AddPipelineWhen(observable, configure, instantiationScope); } /// - public ILightTransitionPipelineConfigurator AddPipelineWhen(IObservable observable, Action> configure) + public ILightTransitionPipelineConfigurator AddPipelineWhen(IObservable observable, Action> configure, InstantiationScope instantiationScope = InstantiationScope.Shared) { - /* - * Note: we use CompositeLightTransitionPipelineConfigurator.AddReactiveNode so configure is also applied on the composite context. - * This currently doesn't matter, but it seems like good practice to keep the context consistent for all nodes created within the configure action, and it also allows for more flexible future implementations of the composite pattern. - */ return AddReactiveNode(c => c - .On(observable.Where(x => x), configure) + .On(observable.Where(x => x), configure, instantiationScope) .PassThroughOn(observable.Where(x => !x))); } diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Switch.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Switch.cs index 661f841..36e847b 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Switch.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Switch.cs @@ -189,7 +189,8 @@ ILightTransitionPipelineConfigurator Switch(IObse /// The configurator instance for method chaining. ILightTransitionPipelineConfigurator AddReactiveNodeSwitch( Action> trueConfigure, - Action> falseConfigure) + Action> falseConfigure, + InstantiationScope instantiationScope = InstantiationScope.Shared) where TObservable : IObservable; /// @@ -203,7 +204,8 @@ ILightTransitionPipelineConfigurator AddReactiveNodeSwitch( /// The configurator instance for method chaining. ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(IObservable observable, Action> trueConfigure, - Action> falseConfigure); + Action> falseConfigure, + InstantiationScope instantiationScope = InstantiationScope.Shared); /// /// Registers a pipeline that switches between two configurations based on a boolean observable. @@ -218,7 +220,8 @@ ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(IObservableThe configurator instance for method chaining. ILightTransitionPipelineConfigurator AddPipelineSwitch( Action> trueConfigure, - Action> falseConfigure) + Action> falseConfigure, + InstantiationScope instantiationScope = InstantiationScope.Shared) where TObservable : IObservable; /// @@ -232,7 +235,8 @@ ILightTransitionPipelineConfigurator AddPipelineSwitch( /// The configurator instance for method chaining. ILightTransitionPipelineConfigurator AddPipelineSwitch(IObservable observable, Action> trueConfigure, - Action> falseConfigure); + Action> falseConfigure, + InstantiationScope instantiationScope = InstantiationScope.Shared); /// /// Registers a node that turns the light on when the observable of type diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.When.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.When.cs index 817538d..0d8fa8f 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.When.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.When.cs @@ -169,7 +169,8 @@ ILightTransitionPipelineConfigurator AddReactiveNodeWhen( /// An action to configure the reactive node. /// The configurator instance for method chaining. ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservable observable, - Action> configure); + Action> configure, + InstantiationScope instantiationScope = InstantiationScope.Shared); /// /// Registers a pipeline configured by when the observable of type @@ -181,7 +182,8 @@ ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservableAn action to configure the nested pipeline. /// The configurator instance for method chaining. ILightTransitionPipelineConfigurator AddPipelineWhen( - Action> configure) + Action> configure, + InstantiationScope instantiationScope = InstantiationScope.Shared) where TObservable : IObservable; /// @@ -193,7 +195,8 @@ ILightTransitionPipelineConfigurator AddPipelineWhen( /// An action to configure the nested pipeline. /// The configurator instance for method chaining. ILightTransitionPipelineConfigurator AddPipelineWhen(IObservable observable, - Action> configure); + Action> configure, + InstantiationScope instantiationScope = InstantiationScope.Shared); /// /// Registers a node that turns off the light when the observable diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs index 170cd09..0918840 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.Switch.cs @@ -2,6 +2,7 @@ using CodeCasa.AutomationPipelines.Lights.ReactiveNode; using CodeCasa.Lights; using Microsoft.Extensions.DependencyInjection; +using System; using System.Reactive.Concurrency; using System.Reactive.Linq; @@ -102,36 +103,35 @@ public ILightTransitionPipelineConfigurator Switch - public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(Action> trueConfigure, Action> falseConfigure) where TObservable : IObservable + public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(Action> trueConfigure, Action> falseConfigure, InstantiationScope instantiationScope = InstantiationScope.Shared) where TObservable : IObservable { var observable = ActivatorUtilities.CreateInstance(_serviceProvider); - return AddReactiveNodeSwitch(observable, trueConfigure, falseConfigure); + return AddReactiveNodeSwitch(observable, trueConfigure, falseConfigure, instantiationScope); } /// public ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(IObservable observable, Action> trueConfigure, - Action> falseConfigure) + Action> falseConfigure, InstantiationScope instantiationScope = InstantiationScope.Shared) { return AddReactiveNode(c => c - .On(observable.Where(x => x), trueConfigure) - .On(observable.Where(x => !x), falseConfigure)); + .On(observable.Where(x => x), trueConfigure, instantiationScope) + .On(observable.Where(x => !x), falseConfigure, instantiationScope)); } /// - public ILightTransitionPipelineConfigurator AddPipelineSwitch(Action> trueConfigure, Action> falseConfigure) where TObservable : IObservable + public ILightTransitionPipelineConfigurator AddPipelineSwitch(Action> trueConfigure, Action> falseConfigure, InstantiationScope instantiationScope = InstantiationScope.Shared) where TObservable : IObservable { - return Switch( - s => s.GetRequiredService().CreateLightPipeline(Light, trueConfigure), - s => s.GetRequiredService().CreateLightPipeline(Light, falseConfigure)); + var observable = ActivatorUtilities.CreateInstance(_serviceProvider); + return AddPipelineSwitch(observable, trueConfigure, falseConfigure, instantiationScope); } /// public ILightTransitionPipelineConfigurator AddPipelineSwitch(IObservable observable, Action> trueConfigure, - Action> falseConfigure) + Action> falseConfigure, InstantiationScope instantiationScope = InstantiationScope.Shared) { - return Switch(observable, - s => s.GetRequiredService().CreateLightPipeline(Light, trueConfigure), - s => s.GetRequiredService().CreateLightPipeline(Light, falseConfigure)); + return AddReactiveNode(c => c + .On(observable.Where(x => x), trueConfigure, instantiationScope) + .On(observable.Where(x => !x), falseConfigure, instantiationScope)); } /// diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs index 2434b16..0a5107c 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs @@ -1,9 +1,10 @@ using CodeCasa.AutomationPipelines.Lights.Nodes; using CodeCasa.AutomationPipelines.Lights.ReactiveNode; -using System.Reactive.Concurrency; -using System.Reactive.Linq; using CodeCasa.Lights; using Microsoft.Extensions.DependencyInjection; +using System; +using System.Reactive.Concurrency; +using System.Reactive.Linq; namespace CodeCasa.AutomationPipelines.Lights.Pipeline; @@ -108,24 +109,36 @@ public ILightTransitionPipelineConfigurator AddReactiveNodeWhen - public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservable observable, Action> configure) + public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservable observable, Action> configure, InstantiationScope instantiationScope = InstantiationScope.Shared) { return AddReactiveNode(c => c - .On(observable.Where(x => x), configure) + .On(observable.Where(x => x), configure, instantiationScope) .PassThroughOn(observable.Where(x => !x))); } /// - public ILightTransitionPipelineConfigurator AddPipelineWhen(Action> pipelineConfigurator) where TObservable : IObservable + public ILightTransitionPipelineConfigurator AddPipelineWhen(Action> pipelineConfigurator, InstantiationScope instantiationScope = InstantiationScope.Shared) where TObservable : IObservable { + if (instantiationScope == InstantiationScope.Shared) + { + // Note: even though InstantiationScope is primarily intended for composite pipelines, we try to stay consistent with the lifetime of the inner pipeline to avoid confusion. + var pipeline = _serviceProvider.GetRequiredService().CreateLightPipeline(Light, pipelineConfigurator); + return When(_ => pipeline); + } return When(c => c.GetRequiredService() .CreateLightPipeline(c.GetRequiredService(), pipelineConfigurator)); } /// - public ILightTransitionPipelineConfigurator AddPipelineWhen(IObservable observable, Action> pipelineConfigurator) + public ILightTransitionPipelineConfigurator AddPipelineWhen(IObservable observable, Action> pipelineConfigurator, InstantiationScope instantiationScope = InstantiationScope.Shared) { + if (instantiationScope == InstantiationScope.Shared) + { + // Note: even though InstantiationScope is primarily intended for composite pipelines, we try to stay consistent with the lifetime of the inner pipeline to avoid confusion. + var pipeline = _serviceProvider.GetRequiredService().CreateLightPipeline(Light, pipelineConfigurator); + return When(observable, _ => pipeline); + } return When(observable, c => c.GetRequiredService() .CreateLightPipeline(c.GetRequiredService(), pipelineConfigurator)); diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs index f50b179..8139853 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeLightTransitionReactiveNodeConfigurator.On.cs @@ -40,9 +40,9 @@ public ILightTransitionReactiveNodeConfigurator On(IObservable tri } /// - public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> pipelineConfigurator, CompositeInstantiationScope instantiationScope = CompositeInstantiationScope.Shared) + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> pipelineConfigurator, InstantiationScope instantiationScope = InstantiationScope.Shared) { - if (instantiationScope == CompositeInstantiationScope.Shared) + if (instantiationScope == InstantiationScope.Shared) { // Note: we create the pipeline in composite context so all configuration is also applied in that context. var pipelines = lightPipelineFactory.CreateLightPipelines(configurators.Values.Select(c => c.Light), @@ -56,9 +56,9 @@ public ILightTransitionReactiveNodeConfigurator On(IObservable tri } /// - public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> configure, CompositeInstantiationScope instantiationScope = CompositeInstantiationScope.Shared) + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> configure, InstantiationScope instantiationScope = InstantiationScope.Shared) { - if (instantiationScope == CompositeInstantiationScope.Shared) + if (instantiationScope == InstantiationScope.Shared) { // Note: we create the pipeline in composite context so all configuration is also applied in that context. var nodes = reactiveNodeFactory.CreateReactiveNodes(configurators.Values.Select(c => c.Light), diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs index 6a9b74c..9c79554 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/ILightTransitionReactiveNodeConfigurator.On.cs @@ -73,14 +73,14 @@ ILightTransitionReactiveNodeConfigurator On(IObservable triggerObs /// The observable that triggers the pipeline activation. /// An action to configure the nested pipeline. /// - /// Specifies where the nested pipeline is instantiated. Use + /// Specifies where the nested pipeline is instantiated. Use /// to create pipelines immediately in the composite context (shared across all lights), or - /// to defer creation to individual child service scopes. + /// to defer creation to individual child service scopes. /// /// The configurator instance for method chaining. ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> pipelineConfigurator, - CompositeInstantiationScope instantiationScope = CompositeInstantiationScope.Shared); + InstantiationScope instantiationScope = InstantiationScope.Shared); /// /// Registers a trigger that activates a nested reactive node configured by when the emits a value. @@ -89,14 +89,14 @@ ILightTransitionReactiveNodeConfigurator On(IObservable triggerObs /// The observable that triggers the reactive node activation. /// An action to configure the nested reactive node. /// - /// Specifies where the nested reactive node is instantiated. Use + /// Specifies where the nested reactive node is instantiated. Use /// to create nodes immediately in the composite context (shared across all lights), or - /// to defer creation to individual child service scopes. + /// to defer creation to individual child service scopes. /// /// The configurator instance for method chaining. ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> configure, - CompositeInstantiationScope instantiationScope = CompositeInstantiationScope.Shared); + InstantiationScope instantiationScope = InstantiationScope.Shared); /// /// Registers a pass-through trigger that allows the current input to pass through unchanged when the emits a value. diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeInstantiationScope.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/InstantiationScope.cs similarity index 97% rename from src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeInstantiationScope.cs rename to src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/InstantiationScope.cs index 804dc4b..6e523d6 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/CompositeInstantiationScope.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/InstantiationScope.cs @@ -3,7 +3,7 @@ /// /// Specifies the service provider scope in which composite child objects (pipelines, reactive nodes) are instantiated. /// -public enum CompositeInstantiationScope +public enum InstantiationScope { /// /// Objects are instantiated immediately in the shared composite context. diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs index 009d661..bcd80c1 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/LightTransitionReactiveNodeConfigurator.On.cs @@ -1,9 +1,9 @@ using CodeCasa.AutomationPipelines.Lights.Nodes; using CodeCasa.AutomationPipelines.Lights.Pipeline; -using System.Reactive.Concurrency; -using System.Reactive.Linq; using CodeCasa.Lights; using Microsoft.Extensions.DependencyInjection; +using System.Reactive.Concurrency; +using System.Reactive.Linq; namespace CodeCasa.AutomationPipelines.Lights.ReactiveNode; @@ -41,10 +41,30 @@ public ILightTransitionReactiveNodeConfigurator On(IObservable tri AddNodeSource(triggerObservable.Select(_ => nodeFactory)); /// - public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> pipelineConfigurator, CompositeInstantiationScope _ = CompositeInstantiationScope.Shared) => On(triggerObservable, s => s.GetRequiredService().CreateLightPipeline(Light, pipelineConfigurator)); + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> pipelineConfigurator, InstantiationScope instantiationScope = InstantiationScope.Shared) + { + if (instantiationScope == InstantiationScope.Shared) + { + // Note: even though InstantiationScope is primarily intended for composite pipelines, we try to stay consistent with the lifetime of the inner pipeline to avoid confusion. + var pipeline = ServiceProvider.GetRequiredService().CreateLightPipeline(Light, pipelineConfigurator); + return On(triggerObservable, _ => pipeline); + } + return On(triggerObservable, + s => s.GetRequiredService().CreateLightPipeline(Light, pipelineConfigurator)); + } /// - public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> configure, CompositeInstantiationScope _ = CompositeInstantiationScope.Shared) => On(triggerObservable, s => s.GetRequiredService().CreateReactiveNode(Light, configure)); + public ILightTransitionReactiveNodeConfigurator On(IObservable triggerObservable, Action> configure, InstantiationScope instantiationScope = InstantiationScope.Shared) + { + if (instantiationScope == InstantiationScope.Shared) + { + // Note: even though InstantiationScope is primarily intended for composite pipelines, we try to stay consistent with the lifetime of the inner node to avoid confusion. + var node = ServiceProvider.GetRequiredService().CreateReactiveNode(Light, configure); + return On(triggerObservable, _ => node); + } + return On(triggerObservable, + s => s.GetRequiredService().CreateReactiveNode(Light, configure)); + } /// public ILightTransitionReactiveNodeConfigurator PassThroughOn(IObservable triggerObservable) From 0bae464add87dcdff21ce2626cfbf54f24c41da3 Mon Sep 17 00:00:00 2001 From: Jasper Date: Thu, 12 Feb 2026 11:04:55 +0100 Subject: [PATCH 5/6] Applying xml comments. --- ...ightTransitionPipelineConfigurator.When.cs | 9 ++----- ...htTransitionPipelineConfigurator.Switch.cs | 24 ++++++++++++++----- ...ightTransitionPipelineConfigurator.When.cs | 15 +++++++++++- ...ightTransitionPipelineConfigurator.When.cs | 4 ++-- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs index 1d1454f..092af29 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/CompositeLightTransitionPipelineConfigurator.When.cs @@ -107,15 +107,10 @@ public ILightTransitionPipelineConfigurator When(IObservable - public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(Action> configure) where TObservable : IObservable + public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(Action> configure, InstantiationScope instantiationScope = InstantiationScope.Shared) where TObservable : IObservable { - /* - * For this implementation we can either instantiate the TObservable for each container and pass configure to them individual, breaking composite dimming behavior. - * Or we can create a single TObservable without light context. - * I decided to go with the latter to preserve composite dimming behavior. - */ var observable = ActivatorUtilities.CreateInstance(serviceProvider); - return AddReactiveNodeWhen(observable, configure); + return AddReactiveNodeWhen(observable, configure, instantiationScope); } /// diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Switch.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Switch.cs index 36e847b..4212ddd 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Switch.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.Switch.cs @@ -178,18 +178,21 @@ ILightTransitionPipelineConfigurator Switch(IObse /// /// Registers a reactive node that switches between two configurations based on a boolean observable. - /// When the observable of type emits , - /// the node is configured by ; when it emits , + /// When the observable of type emits , + /// the node is configured by ; when it emits , /// the node is configured by . /// The observable is resolved from the service provider. /// /// The type of the observable to resolve from the service provider. /// An action to configure the reactive node for true values. /// An action to configure the reactive node for false values. + /// + /// Specifies the instantiation scope for the reactive node. Use to create the node once and reuse it for all switches (lifetime matches the parent pipeline), or to create a new node each time the observable emits true (disposed when replaced). + /// /// The configurator instance for method chaining. ILightTransitionPipelineConfigurator AddReactiveNodeSwitch( Action> trueConfigure, - Action> falseConfigure, + Action> falseConfigure, InstantiationScope instantiationScope = InstantiationScope.Shared) where TObservable : IObservable; @@ -201,6 +204,9 @@ ILightTransitionPipelineConfigurator AddReactiveNodeSwitch( /// The observable that determines which configuration to apply. /// An action to configure the reactive node for true values. /// An action to configure the reactive node for false values. + /// + /// Specifies the instantiation scope for the reactive node. Use to create the node once and reuse it for all switches (lifetime matches the parent pipeline), or to create a new node each time the observable emits true (disposed when replaced). + /// /// The configurator instance for method chaining. ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(IObservable observable, Action> trueConfigure, @@ -209,18 +215,21 @@ ILightTransitionPipelineConfigurator AddReactiveNodeSwitch(IObservable /// Registers a pipeline that switches between two configurations based on a boolean observable. - /// When the observable of type emits , - /// the pipeline is configured by ; when it emits , + /// When the observable of type emits , + /// the pipeline is configured by ; when it emits , /// the pipeline is configured by . /// The observable is resolved from the service provider. /// /// The type of the observable to resolve from the service provider. /// An action to configure the pipeline for true values. /// An action to configure the pipeline for false values. + /// + /// Specifies the instantiation scope for the pipeline. Use to create the pipeline once and reuse it for all switches (lifetime matches the parent pipeline), or to create a new pipeline each time the observable emits true (disposed when replaced). + /// /// The configurator instance for method chaining. ILightTransitionPipelineConfigurator AddPipelineSwitch( Action> trueConfigure, - Action> falseConfigure, + Action> falseConfigure, InstantiationScope instantiationScope = InstantiationScope.Shared) where TObservable : IObservable; @@ -232,6 +241,9 @@ ILightTransitionPipelineConfigurator AddPipelineSwitch( /// The observable that determines which configuration to apply. /// An action to configure the pipeline for true values. /// An action to configure the pipeline for false values. + /// + /// Specifies the instantiation scope for the pipeline. Use to create the pipeline once and reuse it for all switches (lifetime matches the parent pipeline), or to create a new pipeline each time the observable emits true (disposed when replaced). + /// /// The configurator instance for method chaining. ILightTransitionPipelineConfigurator AddPipelineSwitch(IObservable observable, Action> trueConfigure, diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.When.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.When.cs index 0d8fa8f..7181750 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.When.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/ILightTransitionPipelineConfigurator.When.cs @@ -155,9 +155,13 @@ ILightTransitionPipelineConfigurator When(IObservable obser /// /// The type of the observable to resolve from the service provider. /// An action to configure the reactive node. + /// + /// Specifies the instantiation scope for the reactive node. Use to create the node once and reuse it for all activations (lifetime matches the parent pipeline), or to create a new node each time the observable emits true (disposed when replaced). + /// /// The configurator instance for method chaining. ILightTransitionPipelineConfigurator AddReactiveNodeWhen( - Action> configure) + Action> configure, + InstantiationScope instantiationScope = InstantiationScope.Shared) where TObservable : IObservable; /// @@ -167,6 +171,9 @@ ILightTransitionPipelineConfigurator AddReactiveNodeWhen( /// /// The observable that determines when to apply the reactive node. /// An action to configure the reactive node. + /// + /// Specifies the instantiation scope for the reactive node. Use to create the node once and reuse it for all activations (lifetime matches the parent pipeline), or to create a new node each time the observable emits true (disposed when replaced). + /// /// The configurator instance for method chaining. ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservable observable, Action> configure, @@ -180,6 +187,9 @@ ILightTransitionPipelineConfigurator AddReactiveNodeWhen(IObservable /// The type of the observable to resolve from the service provider. /// An action to configure the nested pipeline. + /// + /// Specifies the instantiation scope for the pipeline. Use to create the pipeline once and reuse it for all activations (lifetime matches the parent pipeline), or to create a new pipeline each time the observable emits true (disposed when replaced). + /// /// The configurator instance for method chaining. ILightTransitionPipelineConfigurator AddPipelineWhen( Action> configure, @@ -193,6 +203,9 @@ ILightTransitionPipelineConfigurator AddPipelineWhen( /// /// The observable that determines when to apply the pipeline. /// An action to configure the nested pipeline. + /// + /// Specifies the instantiation scope for the pipeline. Use to create the pipeline once and reuse it for all activations (lifetime matches the parent pipeline), or to create a new pipeline each time the observable emits true (disposed when replaced). + /// /// The configurator instance for method chaining. ILightTransitionPipelineConfigurator AddPipelineWhen(IObservable observable, Action> configure, diff --git a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs index 0a5107c..2bcdd8e 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/Pipeline/LightTransitionPipelineConfigurator.When.cs @@ -102,10 +102,10 @@ public ILightTransitionPipelineConfigurator When(IObservable - public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(Action> configure) where TObservable : IObservable + public ILightTransitionPipelineConfigurator AddReactiveNodeWhen(Action> configure, InstantiationScope instantiationScope = InstantiationScope.Shared) where TObservable : IObservable { var observable = ActivatorUtilities.CreateInstance(_serviceProvider); - return AddReactiveNodeWhen(observable, configure); + return AddReactiveNodeWhen(observable, configure, instantiationScope); } /// From a836a225b33651f8334c328a24acc22e44283894 Mon Sep 17 00:00:00 2001 From: Jasper Date: Thu, 12 Feb 2026 11:28:03 +0100 Subject: [PATCH 6/6] Updating xml comments. --- .../ReactiveNode/InstantiationScope.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/InstantiationScope.cs b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/InstantiationScope.cs index 6e523d6..e12869a 100644 --- a/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/InstantiationScope.cs +++ b/src/CodeCasa.AutomationPipelines.Lights/ReactiveNode/InstantiationScope.cs @@ -20,10 +20,10 @@ public enum InstantiationScope /// Objects are instantiated in individual child containers, each with its own light-specific service scope. /// /// - /// When using this scope, a new instance is created each time the corresponding observable emits, + /// When using this scope, a new instance is created each time it is activated (usually when a corresponding observable emits true), /// and each light receives its own instance within its own service scope. The instance is disposed /// whenever it is replaced by a new node (e.g., when another trigger fires). This is useful when you /// need light-specific dependencies, isolated state per light, or fresh instances on each activation. /// - PerChild, + PerChild } \ No newline at end of file