Jump to content
The simFlight Network Forums

Recommended Posts

Posted

Hi, Pete!

I'm new to FSUIPC but I'm loving it! The Lua libraries, in particular, are great, and allowing me to really improve my sim experience.

I am working on a Lua script that overloads the functions of the buttons on my game controller. (I sim on my laptop, and when I'm not at my desk rig, I use a game controller.) This script, inspired by your "tripleuse.lua" example, lets me assign functions that are invoked on button down, button up and button hold, and can be repeating or "only once". This is all on Windows XP SP3 and FS 9.1 using FSUIPC 3.989y (version verified on the main window).

I'm noticing some odd behavior, and I can't tell if they are mistakes I'm making or bugs in FSUIPC. I thought I should bring them to your attention.

1. My initial approach was to capture button presses and test for button releases using ipc.testbutton, as in "tripleuse". This worked brilliantly for the normal buttons (0-31). For the POV buttons, however, this didn't work well. The capture events happened as expected, but ipc.testbutton always returned false, even when the POV button was down. Is this the correct behavior? The docs aren't specific on it one way or another.

2. Since I couldn't get "testbutton" to do what I needed, I changed the code to use event.timer instead; I track presses/releases myself, and on button press I start a timer. The timer function checks to see if buttons are still down, and performs the button "hold" functions. When the button is released, I use "event.cancel" to terminate the timer. However, i get what looks like a low-level Lua error in the script's log. Here's the code to start the timer:

function ActionButton.startTimer()
	ActionButton.timerCount = ActionButton.timerCount and ActionButton.timerCount + 1 or 1 -- how many buttons we're watching
	if ActionButton.timerCount == 1 then -- no timer running yet
		event.timer( ActionButton.timerInterval, "ActionButton.timerEvent" )
		print( "ActionButton.startTimer: Timer started" )
	end
end

and the code to cancel it:

function ActionButton.endTimer()
	ActionButton.timerCount = ActionButton.timerCount - 1
	if ActionButton.timerCount < 1 then -- no more keys to monitor...
		event.cancel( "ActionButton.timerEvent" ) -- so shut down the timer
		print( "ActionButton.endTimer: Timer stopped" )
	end
end

And this is the error that shows up in the log later on:

39234500 *** LUA Error: attempt to call a nil value

Note the lack of line numbers. When I comment out the event.cancel, the error goes away.

Thanks in advance for any insight you can provide into these. I'm happy to provide the entire script if you like, but it's quite long so I thought I'd start with small fragments.

Best,

Michael

Posted

For the POV buttons, however, this didn't work well. The capture events happened as expected, but ipc.testbutton always returned false, even when the POV button was down. Is this the correct behavior? The docs aren't specific on it one way or another.

You can't use testbutton on the so-called POV buttons. They aren't really buttons but an interpretation of the 360 degree values supplied by the POV. They can change from one to another without being "released" first. You'll note that the documentation for TestButton does specifically say the button numbers are 0-31. You'd need something like "readPOV" to return the current POV value instead.

On your other problem, I suspect that new facilities I added recently are conspiring to foil your code. Structuring names like "ActionButton.timerEvent" means procedure "timerEvent" in table "ActionButton". It's like the library calls themselves: "event.timer" is Procedure "timer" in Table "event", the event table having been added to the Lua environment by FSUIPC before starting the plug-in.

Before the latest releases FSUIPC would not have parsed the name and used the '.' in the standard Lua way. I changed it on request for a user who was buliding modules (called by "Require") which effectively adds tables of functions too.

What I don't understand, though, is why this line doesn't give an error too:

event.timer( ActionButton.timerInterval, "ActionButton.timerEvent" )

Maybe, because you are using the '.' method (which is an alternative to [ ] for arrays -- i.e. ActionButton.timerInterval is the same as ActionButton[timerInterval]) a lot of your names made this way are valid table entries. But in that case why not also for the cancel?

If you can't see an explanation perhaps you'd let me see the entire code. I can trace it through the insides of FSUIPC.

Regards

Pete

Posted

You can't use testbutton on the so-called POV buttons. They aren't really buttons but an interpretation of the 360 degree values supplied by the POV. They can change from one to another without being "released" first. You'll note that the documentation for TestButton does specifically say the button numbers are 0-31. You'd need something like "readPOV" to return the current POV value instead.

Yeah, I noticed that the "testbutton" docs didn't mention POV buttons, but I wondered if it was a simple mistake, since "event.button" can capture them. Does FSUIPC+Lua provide any way to read the POV state? Without either of us going to a ton of work, I mean? I didn't see anything in the docs beyond "event.button". I ask because tracking the button press/release and adding the whole timer thing makes the code harder to follow and probably more prone to errors. (My errors, at least.)

On your other problem, I suspect that new facilities I added recently are conspiring to foil your code. Structuring names like "ActionButton.timerEvent" means procedure "timerEvent" in table "ActionButton". It's like the library calls themselves: "event.timer" is Procedure "timer" in Table "event", the event table having been added to the Lua environment by FSUIPC before starting the plug-in.

Before the latest releases FSUIPC would not have parsed the name and used the '.' in the standard Lua way. I changed it on request for a user who was buliding modules (called by "Require") which effectively adds tables of functions too.

Yes, that was a great addition on your part! It allows for coding libraries, which hopefully means that people can publish libraries of useful function that other non-programmers can benefit from. I admit that I'm trying to code this particular script that way, so that it can be used by others.

What I don't understand, though, is why this line doesn't give an error too:

event.timer( ActionButton.timerInterval, "ActionButton.timerEvent" )

Maybe, because you are using the '.' method (which is an alternative to [ ] for arrays -- i.e. ActionButton.timerInterval is the same as ActionButton[timerInterval]) a lot of your names made this way are valid table entries. But in that case why not also for the cancel?

If you can't see an explanation perhaps you'd let me see the entire code. I can trace it through the insides of FSUIPC.

Nothing obvious comes to mind, but it's often hardest to spot your own bugs. As you point out, both timer statements should either work or fail together. The fact the timer function is set up without issue means that the code for referencing functions within tables is working correctly. Unless that same code is somehow not being used for the timer cancel, it should continue to work there, too. It seems, though, that the reference to the timer function is being zero'd (NIL'd) out, but it still thinks that the timer is being captured. I wonder if the code clears the current outstanding timer? The fact that I get one and only one error makes me wonder if the cancel doesn't clear the _next_ timer interrupt, which goes off as planned but encounters an error when the designated function is nil? (Otherwise, I'd expect to get lots of errors as the nil'd function is called every 50 msec.)

Anyway, I'm attaching my scripts. There are two -- "gameControler.lue" which is the one that's got the issue, and "middleclass.lue" which is a pseudo-class library for object-oriented coding goodness. (I'm using it for the first time here, but I don't know much about it. I don't see why it would be causing the error, since everything is ultimately just a function in a table, but you never know.) If you have time to look at it, I'd appreciate it. If it is a bug in FSUIPC, I'm happy to help test the fixed code for you. And if it's my bug -- and aren't they usually? :) -- I apologize in advance for wasting your time.

Best,

Michael

PS: To test the script, I just assign it to an unused key and start it. It then runs in the background. It's assigned to joystick 2, but you can change that right at the top of the script. I get the timing error when I press button0, which in the script is assigned to Auto-Throttle Arm/Disarm. (You can see all the key assignments at the very bottom.) The script generates reams and reams of logs, which may be useful. It's a long script, and not super documented, so if it's taking too long to read through, I can give you the general gist of each function and what it's trying to do.

gameController.zip

Posted

Yeah, I noticed that the "testbutton" docs didn't mention POV buttons, but I wondered if it was a simple mistake, since "event.button" can capture them. Does FSUIPC+Lua provide any way to read the POV state? Without either of us going to a ton of work, I mean? I didn't see anything in the docs beyond "event.button".

Not at present, no. As I said, you'd need a "readPOV(joystick#)" function. It should be reasonably easy to add. The need hadn't occurred to me before -- like many of the Lua library functions it would be a result of a request like now. I shall add it, but it will have to wait a few days because I'm only half-way through a re-build process due to a catastrophic disk crash on my development PC (only the third disk crash I've had at all, as far as I can recall).

Yes, that was a great addition on your part! It allows for coding libraries, which hopefully means that people can publish libraries of useful function that other non-programmers can benefit from. I admit that I'm trying to code this particular script that way, so that it can be used by others.

Ah, okay. I'm concerned now, then, that it doesn't work as expected.

Nothing obvious comes to mind, but it's often hardest to spot your own bugs. As you point out, both timer statements should either work or fail together. The fact the timer function is set up without issue means that the code for referencing functions within tables is working correctly. Unless that same code is somehow not being used for the timer cancel, it should continue to work there, too.

Correct. It is the same code. There's only one place where function name strings are processed.

It seems, though, that the reference to the timer function is being zero'd (NIL'd) out, but it still thinks that the timer is being captured. I wonder if the code clears the current outstanding timer? The fact that I get one and only one error makes me wonder if the cancel doesn't clear the _next_ timer interrupt, which goes off as planned but encounters an error when the designated function is nil? (Otherwise, I'd expect to get lots of errors as the nil'd function is called every 50 msec.)

The "cancel" removes the string name of the timer function from the internal data on that Lua thread -- zeroes it -- so that when the next timer interrupt for it occurs the cancellation is detected and the related event will never fire again. I think this is all interlocked, but I'll check to see whether there's any chance of a timer event being triggered and the cancel occurring between that and the Lua thread being woken to receive it. The fact that there's no line number in the error report seems to suggest this possibility, for sure. Odd, though. If that's the case one would expect it to be intermittent, not solid -- unless possibly your timer interval is very short? Ah, no - 50mSecs. Not short at all.

PS: To test the script, I just assign it to an unused key and start it. It then runs in the background. It's assigned to joystick 2, but you can change that right at the top of the script. I get the timing error when I press button0, which in the script is assigned to Auto-Throttle Arm/Disarm. (You can see all the key assignments at the very bottom.) The script generates reams and reams of logs, which may be useful. It's a long script, and not super documented, so if it's taking too long to read through, I can give you the general gist of each function and what it's trying to do.

Don't worry. I'll be tracking my C code more than your Lua code! ;-)

It will be later this week -- after I've finished rebuilding my development environment. Do you need it first for FSUIPC4, FSUIPC3 or WideClient?

Thanks & Regards

Pete

Posted

Not at present, no. As I said, you'd need a "readPOV(joystick#)" function. It should be reasonably easy to add. The need hadn't occurred to me before -- like many of the Lua library functions it would be a result of a request like now. I shall add it, but it will have to wait a few days because I'm only half-way through a re-build process due to a catastrophic disk crash on my development PC (only the third disk crash I've had at all, as far as I can recall).

Ouch! I hope the rebuild goes fast and well. If you feel like building a "readPOV" function, I certainly won't complain -- that's more than I hoped. Thank you!

The "cancel" removes the string name of the timer function from the internal data on that Lua thread -- zeroes it -- so that when the next timer interrupt for it occurs the cancellation is detected and the related event will never fire again. I think this is all interlocked, but I'll check to see whether there's any chance of a timer event being triggered and the cancel occurring between that and the Lua thread being woken to receive it. The fact that there's no line number in the error report seems to suggest this possibility, for sure. Odd, though. If that's the case one would expect it to be intermittent, not solid -- unless possibly your timer interval is very short? Ah, no - 50mSecs. Not short at all.

And it seems too predictable to be a weird timer interaction. But I'm just guessing.

It will be later this week -- after I've finished rebuilding my development environment. Do you need it first for FSUIPC4, FSUIPC3 or WideClient?

No rush! I need it for FSUIPC3. I'm going to try to figure out another bit of weirdness with it. If/when I fix bugs with my script, I'll post a revised version to you.

Best regards,

Michael

Posted

Ouch! I hope the rebuild goes fast and well. If you feel like building a "readPOV" function, I certainly won't complain -- that's more than I hoped. Thank you!

Just got my development system usable. Not my real comfort settings, yet, but I'll sort the bits and pieces out as I go.

I'll look at adding "readPOV" later today. Meanwhile:

And it seems too predictable to be a weird timer interaction. But I'm just guessing.

In order to get your Lua running I had to change lines 418 and 423, changing "Button" to "ActionButton", else it failed on the following line saying "action" was a nil value. Here's the log for that:

   249500 LUA: gameController: Started
   249500 LUA: Button:initialize: Pairing button 'shiftbutton9' for joystick '1'
   249500 LUA: ActionButton:initialize: Setting up action button 0
   249500 LUA: Button:initialize: Pairing button 'button0' for joystick '1'
   249515 LUA: ActionButton:actionIf: Setting '[unconditional]' action for 'CLICK-PRESS' on button 0
   249515 LUA: ActionButton:actionIf: Setting 'shiftbutton9' action for 'CLICK-PRESS' on button 0
   249515 LUA: ActionButton:initialize: Setting up action button 1
   249515 LUA: Button:initialize: Pairing button 'button1' for joystick '1'
   249515 LUA: ActionButton:actionIf: Setting '[unconditional]' action for 'CLICK-REPEAT-FAST' on button 1
   249515 LUA: ActionButton:actionIf: Setting 'shiftbutton9' action for 'CLICK-PRESS' on button 1
   249515 LUA: ActionButton:initialize: Setting up action button 2
   249515 LUA: Button:initialize: Pairing button 'button2' for joystick '1'
   249515 LUA: ActionButton:actionIf: Setting '[unconditional]' action for 'CLICK-PRESS' on button 2
   249515 LUA: ActionButton:actionIf: Setting '[unconditional]' action for 'HOLD-REPEAT' on button 2
   249515 LUA: ActionButton:actionIf: Setting 'shiftbutton9' action for 'CLICK-PRESS' on button 2
   249515 LUA: ActionButton:initialize: Setting up action button 3
   249515 LUA: Button:initialize: Pairing button 'button3' for joystick '1'
   249515 LUA: ActionButton:actionIf: Setting '[unconditional]' action for 'CLICK-PRESS' on button 3
   249515 LUA: ActionButton:actionIf: Setting '[unconditional]' action for 'HOLD-REPEAT' on button 3
   249515 LUA: ActionButton:initialize: Setting up action button 4
   249515 LUA: Button:initialize: Pairing button 'button4' for joystick '1'
   249515 LUA: ActionButton:actionIf: Setting '[unconditional]' action for 'CLICK-PRESS' on button 4
   249515 LUA: ActionButton:actionIf: Setting '[unconditional]' action for 'HOLD-ONCE' on button 4
   249515 LUA: ActionButton:actionIf: Setting 'shiftbutton9' action for 'CLICK-PRESS' on button 4
   249515 LUA: ActionButton:actionIf: Setting 'shiftbutton9' action for 'HOLD-ONCE' on button 4
   249515 LUA: ActionButton:initialize: Setting up action button 5
   249515 LUA: Button:initialize: Pairing button 'button5' for joystick '1'
   249515 LUA: ActionButton:actionIf: Setting '[unconditional]' action for 'CLICK-PRESS' on button 5
   249515 LUA: ActionButton:actionIf: Setting 'shiftbutton9' action for 'CLICK-PRESS' on button 5
   249515 LUA: ActionButton:actionIf: Setting 'shiftbutton9' action for 'HOLD-ONCE' on button 5
   249515 LUA: Button:initialize: Pairing button 'button6' for joystick '1'
   249515 *** LUA Error: E:\FSX\Modules\gameController.lua:419: attempt to call method 'action' (a nil value)

Once running, it was okay -- no errors when I pressed by Joystick's Button 0. So I guess that whilst being intermittent -- or rather timing dependent -- the timing is just right on your system for it to look consistent. Here's the relevant part of the log:

   524906 LUA: Button.pressOrRelease: Button '0 is state '1'
   524906 LUA:  
   524906 LUA: ActionButton:press: Testing button '0'
   524906 LUA: ShiftButton:isTriggered: 'shiftbutton9' is 'FALSE'
   524906 LUA: ActionButton:pressAction: Performing button 0's action for '[unconditional]'
   524906 LUA: ActionButton.startTimer: Timer started
   524906 LUA: Button:doAction: No action for 'CLICK-REPEAT-FAST'
   524906 LUA: Button:doAction: No action for 'CLICK-REPEAT'
   524906 LUA: Button:doAction: Performing action 'CLICK-PRESS'
   524906 LUA: Button.invokeFunc: f is type 'function'
   524906 LUA: doControl: Sending control '65860'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524953 LUA: Button.pressOrRelease: Button '0 is state '1'
   524953 LUA: Button.pressOrRelease: Button '0 is state '1'
   524953 LUA: Button.pressOrRelease: Button '0 is state '1'
   524953 LUA: Button.pressOrRelease: Button '0 is state '1'
   524953 LUA: Button.pressOrRelease: Button '0 is state '1'
   524953 LUA: Button.pressOrRelease: Button '0 is state '1'
   524953 LUA: Button.pressOrRelease: Button '0 is state '1'
   525187 LUA: Button.pressOrRelease: Button '0 is state '0'
   525187 LUA:  
   525187 LUA: ActionButton:release: Releaseing button '0'
   525187 LUA: ActionButton.endTimer: Timer stopped

I'll tighten up the interlocks with a couple of small Critical Sections and provide an update for you to test. If the readPOV is easy enough I'll include that too.

Regards

Pete

Posted

I'll tighten up the interlocks with a couple of small Critical Sections and provide an update for you to test. If the readPOV is easy enough I'll include that too.

Okay. Updated versions are now up in the Download Links subforum. I'm reasonably certain that the interlocks on the Timer Cancel will fix your problem, but since I cannot make it fail here I am dependent on your confirmation for that.

On the POV testing, I added the ipc.readPOV function, which returns 0 or 32-39 as appropriate, but whilst I was in theat area of code I saw it was easy enough also to extend the ipc.testbutton facility to handle buttons 32-39 as well. So you have two ways now. That part of the update is all tested.

Regards

Pete

Posted

Wow, that's great! Thanks for the new functions. I will try them hopefully tonight. (I have a big project to finish today... Sigh!) Thanks for the bug fixes. I caught a couple yesterday, too, so I'll merge them all together. I love the fact that you also chose to extend testbutton, which makes my code easier.

Michael

Posted

OK, I've tested the new release, and, as expected, the new features work very well. Whatever you did to the timer code seems to have fixed my issue. And the expansion to "testbutton" also did the trick, which allows me to remove the timer code altogether. (I haven't tested the "readPOV" method, but since "testbutton" works across all buttons, I probably won't.)

Once again, thank you for your quick turnaround. That's wonderful!

I noticed something else that isn't a bug in FSUIPC, but I'm not totally sure it's a feature, either. I was puzzling over this part of the log:

   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524937 LUA: Button.pressOrRelease: Button '0 is state '1'
   524953 LUA: Button.pressOrRelease: Button '0 is state '1'
   524953 LUA: Button.pressOrRelease: Button '0 is state '1'
   524953 LUA: Button.pressOrRelease: Button '0 is state '1'
   524953 LUA: Button.pressOrRelease: Button '0 is state '1'
   524953 LUA: Button.pressOrRelease: Button '0 is state '1'
   524953 LUA: Button.pressOrRelease: Button '0 is state '1'
   524953 LUA: Button.pressOrRelease: Button '0 is state '1'
   525187 LUA: Button.pressOrRelease: Button '0 is state '0'

What this shows is that when you press button 0, the "Button.pressOrRelease" method is called 17 times. Why? Well, I did some really sloppy re-factoring in the button initialization code, and forgot to change a variable name:

event.button( joystick, buttonNo, 3, "Button.pressOrRelease" )

"buttonNo" is actually undefined in the new code (should have been "self.buttonNo"), making it that every time you assign any button, it adds an additional capture onto button 0. Hence the 17 calls, as there are 17 buttons defined. Is this the designed behavior of "event.button"? My first thought was that subsequent captures of button 0 should have simply replaced earlier ones, so that only the last "event.button" call actually takes effect. After thinking about it some more, though, I realized how cool it was that I could actually assign multiple button handlers to a button, and they all would be called when the button is pressed. If that's actually what you planned, it's worth documenting that next time you update the docs, as it could come in pretty handy for someone.

Best,

Michael

Posted

"buttonNo" is actually undefined in the new code (should have been "self.buttonNo"), making it that every time you assign any button, it adds an additional capture onto button 0. Hence the 17 calls, as there are 17 buttons defined. Is this the designed behavior of "event.button"?

Since it isn't an optional part of the parameter list, FSUIPC simply attempts to get it as an integer using the Lua C/C++ call "lua_tointeger". I guess that routine (which is part of the Lua package code) treats "nil" like any other non-numeric and gives 0 rather than an error.

Actually, I just looked it up in the Lua reference manual, and it is designed and documented to return 0 for anything other than a recognisable number.

My first thought was that subsequent captures of button 0 should have simply replaced earlier ones, so that only the last "event.button" call actually takes effect.

Ah, you meant "designed behaviour" differently! Sorry. No, I expected that there might be cases for different handlers. The only thing there can be only one of is the timer -- one per running Lua thread. Else it became a bit of a nightmare.

After thinking about it some more, though, I realized how cool it was that I could actually assign multiple button handlers to a button, and they all would be called when the button is pressed. If that's actually what you planned, it's worth documenting that next time you update the docs, as it could come in pretty handy for someone.

I'll try to remember.

Regards

Pete

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use. Guidelines Privacy Policy We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.