I am surprised that this isn't what happens in the compiled code anyway. I.e. given your source code
I would expect the compiler to generate code that keeps v in a register through all of that and not read and write to the global variable location
Anyway, I suggest that you look at the generated code (if you can work out how to do that) and see if it is really doing what you think it is doing.
View disassembly would be my first thing on a development board 👍🏻
Yes, in C/C++ it is very easy to control if the data will be kept in a register or loaded again. I wonder if Swift can be told to do so?
Considering value
is actually cnc.data.controller_gyro.value
, a plain variable three ObservableObject
classes deep (code below). And there are a couple of quick if checks, so the compiler may choose to load value
again.
Maybe you have compiled with no optimisation? Maybe your real code is more complicated than what you've posted?
By default the iPhone run target is Debug, which should disable optimisations. I tried Release as well, and looked into the disassembly in both cases, but it is very hard for me to follow what ASM correspond to what Swift line. I usually have them side by side when debugging embedded boards, can I do this with Xcode? I get the impression value
is loaded again. I write and debug Cortex-M ASM in kernel mode, but Swift seems to generate a lot of code for simple operations. If you wish I can post a screenshot?
That takes a queue on which it will invoke the callback. If you pass a serial queue there, you should not get concurrent calls.
In that case I should change my code to this
let queue = OperationQueue()
queue.name = "gyroscope"
queue.maxConcurrentOperationCount = 1
queue.qualityOfService = .userInteractive
motion.startGyroUpdates(to: queue, withHandler: gyro_handler)
How are you actually getting this gyro data?
Original implementation
class controller_gyro_t: ObservableObject
{
@Published var active: Bool = false
let start: TimeInterval = 0.20
let delay: TimeInterval = 0.045
let fast: TimeInterval = 0.00
var last: TimeInterval = 0
var timestamp: TimeInterval = 0
var value: Double = 0
var angle: UInt8 = 0
var ack: Bool = false
func stop()
{
if active
{
active = false
}
angle = 0
value = 0
}
}
struct ContentView: View
{
// ...
#if !os(macOS)
let motion = CMMotionManager()
#endif
func gyro_handler(data: CMGyroData?, err: Error?)
{
if cnc.data.stop || cnc.data.return_home
{
DispatchQueue.main.async
{
stop_controller()
}
return
}
if !cnc.data.controller_gyro.active
{
DispatchQueue.main.async
{
stop_controller_gyro()
}
return
}
if let gyro = data
{
let now = gyro.timestamp
let gyro_dt = now - cnc.data.controller_gyro.last
cnc.data.controller_gyro.last = now
if gyro_dt > cnc.data.controller_gyro.start
{
// the last packet was very long ago
// usually the first sample when we start receiving updates
return
}
// rotation rate: rad/s
var value = cnc.data.controller_gyro.value - gyro.rotationRate.z * gyro_dt * angle_rad_to_deg
if value < angle_min
{
value = angle_min
}
else if value > angle_max
{
value = angle_max
}
cnc.data.controller_gyro.value = value
if cnc.data.controller_gyro.ack
{
cnc.data.controller_gyro.ack = false
cnc.data.controller_gyro.timestamp -= cnc.data.controller_gyro.fast
}
if cnc.data.controller_gyro.timestamp > now
{
return
}
let angle = UInt8(value.rounded())
if cnc.data.controller_gyro.angle != angle
{
cnc.data.controller_gyro.angle = angle
cnc.data.controller_gyro.timestamp = now.advanced(by: cnc.data.controller_gyro.delay)
let a = "a\(angle)"
send_no_log(a)
DispatchQueue.main.async
{
cnc.data.angle = Float(angle)
}
}
}
}
func btn_gyro_control() -> some View
{
Button
{
haptic_feedback()
if cnc.data.controller_gyro.active
{
stop_controller()
send_cmd(cmd_stop)
}
else
{
stop_controller()
cnc.data.stop = false
cnc.data.return_home = false
cnc.data.controller_gyro.active = true
cnc.data.controller_gyro.value = angle_mid
cnc.data.controller_gyro.angle = UInt8(angle_min)
send_cmd("u")
#if !os(macOS)
if motion.isGyroAvailable
{
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + cnc.data.controller_gyro.start)
{
motion.startGyroUpdates(to: OperationQueue(), withHandler: gyro_handler)
}
}
#endif
}
} label:
{
Image(systemName: cnc.data.controller_gyro.active ? "gyroscope" : "gyroscope")
.button_style_image(
cnc.connected ? cnc.data.controller_gyro.active ? colour_store : colour_btn : colour_disabled,
fg: cnc.connected ? .white : colour_btn_fg_disabled
)
}
.font(.system(size: size_font_img, design: Font.Design.rounded))
.keyboardShortcut("u", modifiers: [.command])
}
// ...
}
PS: Thank you for your brainstorming ideas 😊 And sorry to the late replay. I had some issues porting the Mac Catalyst version of the app to Mac. Broken Slider
and keyboardShortcut
to name a few.
Hint: the following code works well on iPhone and Mac Catalyst. Try Mac.
Hint: play with step
or without.
@State var inclination: Double = 0
//...
Slider(value: $inclination, in: -80...80, step: 0.01)
Button("press option+1"){ print("option+1") }.keyboardShortcut("1", modifiers: [.option])
Button("¡surprise!"){ print("¡surprise!") }.keyboardShortcut("¡", modifiers: [.option])