From 13a4c4ff3dbed297d9f58dfdb53779d1c3b42b06 Mon Sep 17 00:00:00 2001 From: Antonio Date: Fri, 13 Mar 2020 21:42:57 +0100 Subject: [PATCH] Fix dataSeries using wrong order when reloading charts Fix showing old data for a moment when reloading the chart Cosmetic code changes --- .../Model/AdafruitBoard/AdafruitBoard.swift | 8 +- .../AdafruitBoard/SensorDataSeries.swift | 92 +++++++++++++------ .../AccelerometerViewController.swift | 23 +++-- .../ModuleChartPanelViewController.swift | 23 ++++- .../Quaternion/QuaternionViewController.swift | 10 +- 5 files changed, 104 insertions(+), 52 deletions(-) diff --git a/BluefruitPlayground/Model/AdafruitBoard/AdafruitBoard.swift b/BluefruitPlayground/Model/AdafruitBoard/AdafruitBoard.swift index 53b09e8..b340eef 100644 --- a/BluefruitPlayground/Model/AdafruitBoard/AdafruitBoard.swift +++ b/BluefruitPlayground/Model/AdafruitBoard/AdafruitBoard.swift @@ -111,10 +111,10 @@ class AdafruitBoard { } // Params - Delegates - weak var temperatureDelegate: AdafruitTemperatureDelegate? weak var lightDelegate: AdafruitLightDelegate? - weak var buttonsDelegate: AdafruitButtonsDelegate? weak var accelerometerDelegate: AdafruitAccelerometerDelegate? + weak var buttonsDelegate: AdafruitButtonsDelegate? + weak var temperatureDelegate: AdafruitTemperatureDelegate? weak var humidityDelegate: AdafruitHumidityDelegate? weak var barometricPressureDelegate: AdafruitBarometricPressureDelegate? weak var soundDelegate: AdafruitSoundDelegate? @@ -191,7 +191,7 @@ class AdafruitBoard { } // Setup services - let selectedServices = services != nil ? services! : BoardService.allCases // If services is nil, select all services + let selectedServices = /*Config.isDebugEnabled ? [.temperature] :*/ (services != nil ? services! : BoardService.allCases) // If services is nil, select all services self.setupServices(blePeripheral: blePeripheral, services: selectedServices, completion: completion) } } @@ -368,7 +368,7 @@ class AdafruitBoard { case .quaternion: return isQuaternionEnabled } } - + // MARK: - Read Data func lightLastValue() -> Float? { return blePeripheral?.adafruitLightLastValue() diff --git a/BluefruitPlayground/Model/AdafruitBoard/SensorDataSeries.swift b/BluefruitPlayground/Model/AdafruitBoard/SensorDataSeries.swift index f45eea8..02a60dd 100644 --- a/BluefruitPlayground/Model/AdafruitBoard/SensorDataSeries.swift +++ b/BluefruitPlayground/Model/AdafruitBoard/SensorDataSeries.swift @@ -8,7 +8,7 @@ import Foundation -struct SensorDataSeries: Sequence, IteratorProtocol { +class SensorDataSeries: Sequence { // Config let kMaxNumItems = 1000 @@ -20,39 +20,57 @@ struct SensorDataSeries: Sequence, IteratorProtocol { private var lastInsertIndex = -1 // Last index where a value was inserted private var values = [Entry]() - private var valuesLock = NSLock() + private let queue = DispatchQueue(label: "\(Bundle.main.bundleIdentifier!).SensorDataSeries", attributes: .concurrent) init() { values.reserveCapacity(kMaxNumItems) } // Acccesors - mutating func addValue(_ value: Entry) { - valuesLock.lock(); defer { valuesLock.unlock() } - let insertIndex = (lastInsertIndex + 1) % kMaxNumItems - if insertIndex == values.count { // Array not full. Add value - values.insert(value, at: insertIndex) + func addValue(_ value: Entry) { + queue.async(flags: .barrier) { + let insertIndex = (self.lastInsertIndex + 1) % self.kMaxNumItems + if insertIndex == self.values.count { // Array not full. Add value + self.values.insert(value, at: insertIndex) + } + else { // Array full, replace value + self.values[insertIndex] = value + } + self.lastInsertIndex = insertIndex } - else { // Array full, replace value - values[insertIndex] = value - } - lastInsertIndex = insertIndex } - subscript(index: Int) -> Entry { - return values[internalIndex(index)] + subscript(index: Int) -> Entry? { + var result: Entry? + queue.sync { + guard self.values.startIndex.. 0 else { return nil } - return self[0] + var result: Entry? + queue.sync { + let index = internalIndex(0) + result = values.count > index ? values[index] : nil + } + return result + } + + var last: Entry? { + var result: Entry? + queue.sync { + let index = internalIndex(values.count-1) + result = values.count > index ? values[index] : nil + } + return result } - var last: Entry? { - valuesLock.lock(); defer { valuesLock.unlock() } - guard values.count > 0 else { return nil } - return self[values.count-1] + var count: Int { + var result = 0 + queue.sync { result = self.values.count } + return result } // MARK: - Utils @@ -60,7 +78,7 @@ struct SensorDataSeries: Sequence, IteratorProtocol { let startIndex = lastInsertIndex - (values.count - 1) return mod((startIndex + index), kMaxNumItems) // Use mod instead of %, because numerator could be negative } - + private func mod(_ a: Int, _ n: Int) -> Int { // From: https://stackoverflow.com/questions/41180292/negative-number-modulo-in-swift precondition(n > 0, "modulus must be positive") @@ -69,15 +87,31 @@ struct SensorDataSeries: Sequence, IteratorProtocol { } // MARK: - IteratorProtocol - private var iteratorPosition = 0 - mutating func makeIterator() -> SensorDataSeries { - iteratorPosition = 0 - return self + func makeIterator() -> Iterator { + return Iterator(self, queue: queue) } + - mutating func next() -> Entry? { - guard iteratorPosition < values.count else { return nil } - defer { iteratorPosition = iteratorPosition + 1 } - return self[internalIndex(iteratorPosition)] + // MARK: - + struct Iterator: IteratorProtocol { + private let dataSeries: SensorDataSeries + private var iteratorPosition = 0 + private var queue: DispatchQueue + + init(_ dataSeries: SensorDataSeries, queue: DispatchQueue) { + self.dataSeries = dataSeries + self.queue = queue + } + + mutating func next() -> Entry? { + var result: Entry? + queue.sync { + guard iteratorPosition < dataSeries.values.count else { return } + defer { iteratorPosition = iteratorPosition + 1 } + result = dataSeries.values[dataSeries.internalIndex(iteratorPosition)] + } + + return result + } } } diff --git a/BluefruitPlayground/ViewControllers/Accelerometer/AccelerometerViewController.swift b/BluefruitPlayground/ViewControllers/Accelerometer/AccelerometerViewController.swift index 08283ef..d842910 100644 --- a/BluefruitPlayground/ViewControllers/Accelerometer/AccelerometerViewController.swift +++ b/BluefruitPlayground/ViewControllers/Accelerometer/AccelerometerViewController.swift @@ -17,8 +17,8 @@ class AccelerometerViewController: ModuleViewController { @IBOutlet weak var sceneView: SCNView! // Data - private var acceleration = BlePeripheral.AccelerometerValue(x: 0, y: 0, z: 0) - private var circuitNode: SCNNode? + private var acceleration: BlePeripheral.AccelerometerValue? + private var boardNode: SCNNode? private var valuesPanelViewController: AccelerometerPanelViewController! // MARK: - Lifecycle @@ -27,11 +27,10 @@ class AccelerometerViewController: ModuleViewController { // Add panels valuesPanelViewController = (addPanelViewController(storyboardIdentifier: AccelerometerPanelViewController.kIdentifier) as! AccelerometerPanelViewController) - + // Load scene if let scene = AdafruitBoardsManager.shared.currentBoard?.assetScene { - - circuitNode = scene.rootNode.childNode(withName: "root", recursively: false)! + boardNode = scene.rootNode.childNode(withName: "root", recursively: false)! // Setup scene sceneView.scene = scene @@ -44,7 +43,7 @@ class AccelerometerViewController: ModuleViewController { self.title = localizationManager.localizedString("accelerometer_title") moduleHelpMessage = localizationManager.localizedString("accelerometer_help") } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) @@ -53,6 +52,7 @@ class AccelerometerViewController: ModuleViewController { if let acceleration = board?.accelerometerLastValue() { self.acceleration = acceleration } + SCNTransaction.animationDuration = 0 // The first render should be inmediate and not animated updateValueUI() // Set delegate @@ -69,16 +69,19 @@ class AccelerometerViewController: ModuleViewController { // MARK: - UI private func updateValueUI() { + guard let acceleration = acceleration else { return } + + SCNTransaction.animationDuration = BlePeripheral.kAdafruitSensorDefaultPeriod + // Calculate Euler Angles let eulerAngles = AccelerometerUtils.accelerationToEuler(acceleration) //DLog("Euler: pitch: \(eulerAngles.x) yaw: \(eulerAngles.y) roll: \(eulerAngles.z)") // Update circuit model orientation - SCNTransaction.animationDuration = BlePeripheral.kAdafruitSensorDefaultPeriod - circuitNode?.eulerAngles = eulerAngles + boardNode?.eulerAngles = eulerAngles // Update panel - valuesPanelViewController.accelerationReceived(acceleration: self.acceleration, eulerAngles: eulerAngles) + valuesPanelViewController.accelerationReceived(acceleration: acceleration, eulerAngles: eulerAngles) } } @@ -89,3 +92,5 @@ extension AccelerometerViewController: AdafruitAccelerometerDelegate { updateValueUI() } } + + diff --git a/BluefruitPlayground/ViewControllers/Common/ModuleChartPanelViewController.swift b/BluefruitPlayground/ViewControllers/Common/ModuleChartPanelViewController.swift index 5c38b43..93051d1 100644 --- a/BluefruitPlayground/ViewControllers/Common/ModuleChartPanelViewController.swift +++ b/BluefruitPlayground/ViewControllers/Common/ModuleChartPanelViewController.swift @@ -26,10 +26,17 @@ class ChartPanelViewController: ModulePanelViewController { setupChart() } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + notifyDataSetChanged() // Important: reload dataset to avoid showing for a moment a weird chart + } + // MARK: - Data internal func dataSeriesValueToChartValue(_ value: Float) -> Double { return Double(value) } + private var valuesLock = NSLock() // MARK: - Line Chart private func setupChart() { @@ -50,11 +57,12 @@ class ChartPanelViewController: ModulePanelViewController { } internal func reloadChartEntries(dataSeries: SensorDataSeries) { + valuesLock.lock(); defer { valuesLock.unlock() } // Don't change the timestamp while addingEntries - let maxTimestamp = dataSeries.max { (a, b) -> Bool in + let minTimestamp = dataSeries.min { (a, b) -> Bool in return a.timestamp < b.timestamp - }?.timestamp - originTimestamp = maxTimestamp ?? CFAbsoluteTimeGetCurrent() + }?.timestamp + originTimestamp = min(originTimestamp, minTimestamp ?? CFAbsoluteTimeGetCurrent()) let entries = chartEntries(dataSeries: dataSeries) // Add Dataset @@ -69,11 +77,14 @@ class ChartPanelViewController: ModulePanelViewController { // Set dataset chartView.data = LineChartData(dataSet: dataSet) + } - + private func notifyDataSetChanged() { + /* let isViewVisible = self.viewIfLoaded?.window != nil // https://stackoverflow.com/questions/2777438/how-to-tell-if-uiviewcontrollers-view-is-visible guard isViewVisible else { return } + */ guard let dataSet = dataSet else { return } chartView.data?.notifyDataChanged() @@ -85,8 +96,9 @@ class ChartPanelViewController: ModulePanelViewController { let xOffset = (dataSet.entries.last?.x ?? 0) - (visibleInterval-1) chartView.moveViewToX(xOffset) } + } - + // MARK:- Utils private func chartEntries(dataSeries: SensorDataSeries) -> [ChartDataEntry] { let chartEntries = dataSeries.map { entry -> ChartDataEntry in @@ -98,6 +110,7 @@ class ChartPanelViewController: ModulePanelViewController { // MARK: - Actions func addEntry(_ entry: SensorDataSeries.Entry) { + valuesLock.lock(); defer { valuesLock.unlock() } // Don't change the timestamp while addingEntries guard let dataSet = dataSet else { return } let value = dataSeriesValueToChartValue(entry.value) diff --git a/BluefruitPlayground/ViewControllers/Quaternion/QuaternionViewController.swift b/BluefruitPlayground/ViewControllers/Quaternion/QuaternionViewController.swift index 9f79d2e..268ad41 100644 --- a/BluefruitPlayground/ViewControllers/Quaternion/QuaternionViewController.swift +++ b/BluefruitPlayground/ViewControllers/Quaternion/QuaternionViewController.swift @@ -17,7 +17,7 @@ class QuaternionViewController: ModuleViewController { @IBOutlet weak var sceneView: SCNView! // Data - private var quaternion = BlePeripheral.QuaternionValue(x: 0, y: 0, z: 0, w: 1) + private var quaternion: BlePeripheral.QuaternionValue? private var boardNode: SCNNode? private var valuesPanelViewController: QuaternionPanelViewController! @@ -68,12 +68,12 @@ class QuaternionViewController: ModuleViewController { // MARK: - UI private func updateValueUI() { + guard let quaternion = quaternion else { return } + + let scnQuaternion = simd_quatf(ix: quaternion.x, iy: quaternion.y, iz: quaternion.z, r: quaternion.w) + // Update circuit model orientation SCNTransaction.animationDuration = BlePeripheral.kAdafruitSensorDefaultPeriod -// let scnQuaternion = SCNQuaternion(quaternion.qx, quaternion.qy, quaternion.qz, quaternion.qw) - let scnQuaternion = simd_quatf(ix: quaternion.x, iy: quaternion.y, iz: quaternion.z, r: quaternion.w) - - //boardNode?.orientation = scnQuaternion boardNode?.simdOrientation = scnQuaternion // Update panel