@@ -59,7 +59,14 @@ public class FlagPole<RootGroup> where RootGroup: FlagContainer {
5959 #if !os(Linux)
6060
6161 if #available( OSX 10 . 15 , iOS 13 . 0 , tvOS 13 . 0 , watchOS 6 . 0 , * ) {
62- self . setupSnapshotPublishing ( keys: self . allFlagKeys, sendImmediately: true )
62+ let oldSourceNames = oldValue. map ( \. name)
63+ let newSourceNames = _sources. map ( \. name)
64+
65+ self . setupSnapshotPublishing (
66+ keys: self . allFlagKeys,
67+ sendImmediately: true ,
68+ changedSources: oldSourceNames. difference ( from: newSourceNames) . map ( \. element)
69+ )
6370 }
6471
6572 #endif
@@ -159,7 +166,7 @@ public class FlagPole<RootGroup> where RootGroup: FlagContainer {
159166
160167 // MARK: - Real Time Changes
161168
162- #if !os(Linux)
169+ #if !os(Linux)
163170
164171 /// An internal state variable used so we don't setup the `Publisher` infrastructure
165172 /// until someone has accessed `self.publisher`
@@ -179,44 +186,106 @@ public class FlagPole<RootGroup> where RootGroup: FlagContainer {
179186 let snapshot = self . latestSnapshot
180187 if self . shouldSetupSnapshotPublishing == false {
181188 self . shouldSetupSnapshotPublishing = true
182- self . setupSnapshotPublishing ( keys: self . allFlagKeys, sendImmediately: true )
189+ self . setupSnapshotPublishing ( keys: self . allFlagKeys, sendImmediately: false )
183190 }
184191 return snapshot. eraseToAnyPublisher ( )
185192 }
186193
187194 private lazy var cancellables = Set < AnyCancellable > ( )
188195
189- private func setupSnapshotPublishing ( keys: Set < String > , sendImmediately: Bool ) {
196+ private func setupSnapshotPublishing ( keys: Set < String > , sendImmediately: Bool , changedSources : [ String ] ? = nil ) {
190197 guard self . shouldSetupSnapshotPublishing else { return }
191198
192199 // cancel our existing one
193200 self . cancellables. forEach { $0. cancel ( ) }
194201 self . cancellables. removeAll ( )
195202
196203 let upstream = self . _sources
197- . compactMap { source in
198- source. valuesDidChange ( keys: keys)
204+ . compactMap { source -> AnyPublisher < ( String , Set < String > ) , Never > ? in
205+ let maybePublisher = source. valuesDidChange ( keys: keys)
199206 ?? source. valuesDidChange? . map ( { _ in [ ] } ) . eraseToAnyPublisher ( ) // backwards compatibility
207+
208+ guard let publisher = maybePublisher else {
209+ return nil
210+ }
211+
212+ let name = source. name
213+ return publisher
214+ . map { ( name, $0) }
215+ . eraseToAnyPublisher ( )
200216 }
201217
202218 Publishers . MergeMany ( upstream)
203- . sink { [ weak self] keys in
219+ . sink { [ weak self] source , keys in
204220 guard let self = self else { return }
205221
206222 let snapshot = Snapshot ( flagPole: self , snapshot: self . latestSnapshot. value)
207- let changed = Snapshot ( flagPole: self , copyingFlagValuesFrom: . pole, keys: keys. isEmpty == true ? nil : keys)
223+ let changed = Snapshot ( flagPole: self , copyingFlagValuesFrom: . pole, keys: keys. isEmpty == true ? nil : keys, diagnosticsEnabled : self . _diagnosticsEnabled )
208224 snapshot. merge ( changed)
209-
210225 self . latestSnapshot. send ( snapshot)
226+
227+ if self . _diagnosticsEnabled == true {
228+ self . diagnosticSubject. send ( . init( changed: changed, sources: [ source] ) )
229+ }
211230 }
212231 . store ( in: & self . cancellables)
213232
214233 if sendImmediately {
215- self . latestSnapshot. send ( self . snapshot ( ) )
234+ let snapshot = self . snapshot ( )
235+ self . latestSnapshot. send ( snapshot)
236+ if self . _diagnosticsEnabled == true {
237+ self . diagnosticSubject. send ( . init( changed: snapshot, sources: changedSources) )
238+ }
239+ }
240+ }
241+
242+ #endif // !os(Linux)
243+
244+ // MARK: - Diagnostics
245+
246+ var _diagnosticsEnabled = false
247+
248+ /// Returns the current diagnostic state of all flags managed by this FlagPole.
249+ ///
250+ /// This method is intended to be called from the debugger
251+ ///
252+ public func makeDiagnostics ( ) -> [ FlagPoleDiagnostic ] {
253+ return . init( current: self . snapshot ( enableDiagnostics: true ) )
254+ }
255+
256+ #if !os(Linux)
257+
258+ private lazy var diagnosticSubject = PassthroughSubject < [ FlagPoleDiagnostic ] , Never > ( )
259+
260+ /// A `Publisher` that can be used to monitor diagnostic outputs
261+ ///
262+ /// An array of `Diagnostic` messages is emitted every time a flag value changes. It can be one of two types:
263+ ///
264+ /// - The value of every flag on the `FlagPole` at the time of subscribing, and which `FlagValueSource` it was resolved by
265+ /// - An array of the flag values that were changed, which `FlagValueSource` they were changed by, and their resolved value/source
266+ ///
267+ public func makeDiagnosticsPublisher ( ) -> AnyPublisher < [ FlagPoleDiagnostic ] , Never > {
268+ let wasAlreadyEnabled = _diagnosticsEnabled
269+ _diagnosticsEnabled = true
270+
271+ let snapshot = self . latestSnapshot. value
272+
273+ // if publishing hasn't been started yet (ie they've accessed `_diagnosticsPublisher` before `publisher`)
274+ if self . shouldSetupSnapshotPublishing == false {
275+ self . shouldSetupSnapshotPublishing = true
276+ self . setupSnapshotPublishing ( keys: self . allFlagKeys, sendImmediately: false )
277+
278+ // if publishing has already been started, but diagnostics were not previously enabled, we setup again to make sure they are available
279+ } else if wasAlreadyEnabled == false {
280+ self . setupSnapshotPublishing ( keys: self . allFlagKeys, sendImmediately: true )
216281 }
282+
283+ return diagnosticSubject
284+ . prepend ( . init( current: snapshot) )
285+ . eraseToAnyPublisher ( )
217286 }
218287
219- #endif
288+ #endif // !os(Linux)
220289
221290
222291 // MARK: - Snapshots
@@ -229,10 +298,11 @@ public class FlagPole<RootGroup> where RootGroup: FlagContainer {
229298 /// or nil then the values of each `Flag` within the `FlagPole` is copied
230299 /// into the snapshot instead.
231300 ///
232- public func snapshot ( of source: FlagValueSource ? = nil ) -> Snapshot < RootGroup > {
301+ public func snapshot ( of source: FlagValueSource ? = nil , enableDiagnostics : Bool = false ) -> Snapshot < RootGroup > {
233302 return Snapshot (
234303 flagPole: self ,
235- copyingFlagValuesFrom: source. flatMap ( Snapshot . Source. source) ?? . pole
304+ copyingFlagValuesFrom: source. flatMap ( Snapshot . Source. source) ?? . pole,
305+ diagnosticsEnabled: enableDiagnostics || self . _diagnosticsEnabled
236306 )
237307 }
238308
0 commit comments