PDA

View Full Version : Still can't get timing to work correctly



LittleFlower
13th June 2004, 02:36 PM
I'm playing UT. I've written several scripts to do one-button dodging. It works. Now I try to make my script more elaborate. When I press a button, my script goes through several stages. In the first stage I emulate keypresses. In all stages I manipulate the axis in different ways. Each stage is supposed to last a fixed amount of time. I couldn't get it to do what I wanted, so I wrote a simplified script, for testing purpose.

The script does this:

in normal state, the script just pass the axis values on (CMS.A1 = JS1.A1, CMS.A2 = JS1.A2)

when the user pushed a button (JS1.B4) the script will

1) send a few characters (CMS.B99 is "KEYS +t -t +e -e +s -s +t -t")
set CMS.A1 and CMS.A2 to zero
2) keep the axis set to zero for 250 milliseconds (10 loops at 25 ms per loop)
3) then set the axis to move left
keep these axis settings for 2 seconds
4) set the axis to move right
keep these axis settings for 2 seconds
5) then go into a state where:
- as long as the user has not released the JS1.B4 button yet, move forward
- when the user has released JS1.B4, go to normal state and pass the axis values on unchanged

I am seeing two problems here.

When I use the keycheck functionality in the control manager, when I press JS1.B4 I sometimes see the "test" keys being pressed. But sometimes that does not happen. I can't find a pattern. Sometimes I need to move the stick axis to get result again, sometimes I need to wait a few second, sometimes a longer time, sometimes I just need to press again, sometimes I need to press many times. The only explanation I can think of is that the loops do not run enough.

The second problem can be seen when using the calibration utility in the control manager. When I press JS1.B4, I see the axis move to the forward position, and stay there until I release the JS1.B4 button. But I do not see the stick move to the left or the right. If my script did what I wanted, I should see the axis go to the left for 2 seconds, then go to the right for 2 seconds, and then go to the forward position for as long as I keep the button presses. It seems the left and right stages are always skipped (or they are so short, I can't see them ?)

Am I doing something wrong here ?
My cms+map files are here: http://www.xs4all.nl/~hhwsmit/misc/chp-timing-example.zip
Thanks in advance,
Henk


// CMS Script File
//
// Â* Â* Game Title: Test for CH Products
// Â* Â* Written By: Henk Smit
// Â* Â* Â* Â* Â* Date: June 2004
//
//
// This is a test program.
// JS1.B4 is the button we watch.
// When it is pressed, we will do the following:
// Â*- press CMS.B99
// Â* Â* this button prints "test".
// Â* Â* we keep it pressed for 250 milliseconds
// Â* Â* during this stage, we move forward only
// Â*- during 2 seconds, move to the left
// Â*- during 2 seconds, move to the right
// Â*- if the button is still pressed, walk forward, until ....
// Â* Â*the button is released, then go to normal axis movement
// Â*
//
//
//
//
// Defines
//
%DEFINE USER_BUTTON JS1.B4
%DEFINE USER_BUTTON_IS_PRESSED D1

%DEFINE STAGE A1
%DEFINE LOOPS_SINCE_LAST_ACTION A2

%DEFINE LOOPS_STAGE_ACTION_ENDS 10
%DEFINE LOOPS_STAGE_LEFT_ENDS 90
%DEFINE LOOPS_STAGE_RIGHT_ENDS 170

%DEFINE STAGE_NORMAL 0
%DEFINE STAGE_ACTION 1
%DEFINE STAGE_LEFT 2
%DEFINE STAGE_RIGHT 3
%DEFINE STAGE_ENDING 4

//
script

//
// Initialize the loopcounter high, so we can click right away.
//
if ( FIRSTSCAN ) then
Â*LOOPS_SINCE_LAST_ACTION = 1000;
endif

if ( CLOCKTICK ) then
Â*LOOPS_SINCE_LAST_ACTION = LOOPS_SINCE_LAST_ACTION + 1;
endif


//
// Check if the user pushed the button.
//
pulse( USER_BUTTON_IS_PRESSED ) = USER_BUTTON;

//
// Do something different in each state.
// State is checked in reverse order.
//

//
// STAGE_ENDING
// As long as the userbutton is pressed, keep moving forward.
// If the userbutton isn't pressed anymore, just pass normal axis.
//
if ( [STAGE == STAGE_ENDING] ) then
Â*if ( USER_BUTTON ) then
Â* Â*CMS.A1 = 128;
Â* Â*CMS.A2 = 0;
Â*else
Â* Â*CMS.A1 = JS1.A1;
Â* Â*CMS.A2 = JS1.A2;
Â* Â*STAGE = STAGE_NORMAL;
Â*endif
endif

//
// STAGE_RIGHT
// During 2 seconds, keep moving right.
//
if ( [STAGE == STAGE_RIGHT] ) then
Â*CMS.A1 = 255;
Â*CMS.A2 = 128;
Â*if ( [LOOPS_SINCE_LAST_ACTION > LOOPS_STAGE_RIGHT_ENDS] ) then
Â* Â*STAGE = STAGE_ENDING;
Â*endif
endif

//
// STAGE_LEFT
// During 2 seconds, keep moving left.
//
if ( [STAGE == STAGE_LEFT] ) then
Â*CMS.A1 = 0;
Â*CMS.A2 = 128;
Â*CMS.B99 = FALSE;
Â*if ( [LOOPS_SINCE_LAST_ACTION > LOOPS_STAGE_LEFT_ENDS] ) then
Â* Â*STAGE = STAGE_RIGHT;
Â*endif
endif

//
// STAGE_ACTION
// Keep the virtual CMS.B99 pressed for 250 milliseconds.
// During this time, we don't move at all.
//
if ( [STAGE == STAGE_ACTION] ) then
Â*CMS.A1 = 128;
Â*CMS.A2 = 128;
Â*if ( [LOOPS_SINCE_LAST_ACTION > LOOPS_STAGE_ACTION_ENDS] ) then
Â* Â*CMS.B99 = FALSE;
Â* Â*STAGE = STAGE_LEFT;
Â*else
Â* Â*CMS.B99 = TRUE;
Â*endif
endif

//
// STAGE_NORMAL
// As long as the user didn't press the button, keep moving normal.
// If the user pressed the button, then press out virtual button CMS.B99
// to spout out some keypresses.
//
if ( [STAGE == STAGE_NORMAL] ) then
Â*if ( USER_BUTTON_IS_PRESSED ) then
Â* Â*LOOPS_SINCE_LAST_ACTION = 0;
Â* Â*CMS.B99 = TRUE;
Â* Â*CMS.A1 = 128;
Â* Â*CMS.A2 = 128;
Â* Â*STAGE = STAGE_ACTION;
Â*else
Â* Â*CMS.A1 = JS1.A1;
Â* Â*CMS.A2 = JS1.A2;
Â*endif
endif

endScript
-------------

MichaelCHProd
13th June 2004, 05:27 PM
When and where did you get your pedals?

Bob Church
13th June 2004, 11:38 PM
I haven't actually tried the script, but a couple of other things that might be causing you some grief. First, FIRSTSCAN is apparently broken, you'll need to use the original method:

if( not b1 ) then
// Initialize stuff
b1 = TRUE;
endIf

The b1 bit (or whatever you use) will be reset to FALSE when the script starts, so the conditional only executes on the first pass like FIRSTSCAN would. FIRSTSCAN will be fixed in the next update.

Also, watch the PULSE statement. It may only be on for a few microseconds and in general won't reliably send a character. A PERIOD timer would probably work better, it's on for at least one full character scan where the PULSE function is only on for one script scan and the two aren't necessarily the same thing.

- Bob

The StickWorks
http://www.stickworks.com

LittleFlower
14th June 2004, 01:45 PM
Thanks.
Not using FIRSTSCAN solved the problem. At least in a quick test.
I don't know if I am happy or sad now. :D I've spent way too much time on this. :wacko: Are there any more known bugs in CM3.0 ?

From your workaround, can I assume that when a script starts, all boolean variable will be set to FALSE and all analog variable will be set to 0 ? It would be handy if that would always be the case.

Is it guaranteed that when I set my char-repeat-rate to 25 ms, the script will run AT LEAST once every 25 ms ? If this isn't the case, my own keeping-track-of-time won't be reliable. When playing UT I usually get stable FPS between 100 and 120. But when I play more modern games, with lower FPS, can I still assume the CMS script every 25 ms ? Even when my FPS is below 40 ?

BTW, I didn't use the PULSE primitive in my scripts. I tried it only to see if the official way was gonna solve my issue.

Last question:

Suppose in my script I do: CMS.B99 = TRUE;
And CMS.B99 sends 10 characters. Sending those characters will take 250 ms (when repeatrate is set to 25 ms). Now what happens to my script during those 250 ms ? Will it still run every 25 ms ? Or will the script be frozen for 250 ms ?

If the script keeps running, what happens if I set CMS.B99 to FALSE before all chararcters are send ? Will the sequence finish and send all characters ? Or will it be interrupted and only a few chararcters will be send ?

So many questions about the exact behaviour of the script language ..... :( Sorry to bother you, but this forum seems the ultimate place to get the answers. Thanks.

Bob Church
17th June 2004, 01:38 AM
>> Are there any more known bugs in CM3.0 ? <<

There are but not many that are probably causing you any real trouble. These are the ones on my list at the moment:

1. Clickstart takes button 1 from the first physical device, not the first logical device.

2. The Mouse Z axis isn&#39;t getting cleared on a mode change. If you have the Mouse Z axis moving as a result of the map and you switch to Direct Mode before it stops, it will just keep moving forever.

3. You can&#39;t set a negative scaling constant in a scripted SCALE statement.

4. If you use Up/Down programming on a bipolar axis, you have to change the default "N/A" center character to "NULL" (or put a real character into it) or it won&#39;t work correctly.

There are a couple more that have been reported, but I haven&#39;t been able to dig into them yet and find out exactly what&#39;s going on or whether they&#39;re really bugs and not just scripting problems. The ones I know or can verify will get fixed in the next update, shouldn&#39;t be too long.

>> From your workaround, can I assume that when a script starts, all boolean variable will be set to FALSE and all analog variable will be set to 0 ? It would be handy if that would always be the case. <<

Yes, all the internal Axxx and Bxxx variables are set to 0 or FALSE when the script is started. The CMS.Axx and CMS.Bxx variables are cleared, too.

>> Is it guaranteed that when I set my char-repeat-rate to 25 ms, the script will run AT LEAST once every 25 ms ? If this isn&#39;t the case, my own keeping-track-of-time won&#39;t be reliable. When playing UT I usually get stable FPS between 100 and 120. But when I play more modern games, with lower FPS, can I still assume the CMS script every 25 ms ? Even when my FPS is below 40 ? <<

The frame rate won&#39;t affect it, there&#39;s no way the CM can know what the FR is anyway, but really none of the timing is guaranteed. The script runs with CLOCKTICK set the first chance it gets after the 25 ms has elapsed, but the exact timing can depend on lots of things - what else Windows is doing, what else is running and at what priority, what your system clock-tick time is, latency in the USB driver stack, at what point the controller actually sends data, etc. When you&#39;re running the game you&#39;re probably not running much else and so the script gets to execute pretty much on schedule. Even if you sent the characters at exactly 25 ms though, you couldn&#39;t count on them having any effect at that point in time. The data gets handed off asynchronously 4 or 5 times before the game ever sees it, and the game itself only looks once per frame so even if the data were there instantaneously, clocked by an atomic 25 ms clock, there&#39;d be an uncertainty of at least one frame time and the frame time wanders all over the place while you&#39;re playing. The timing will never be something that you can set your watch by. Practically, the timeing is usually "close enough", but it will never be something you can set your watch by.

>> Suppose in my script I do: CMS.B99 = TRUE; And CMS.B99 sends 10 characters. Sending those characters will take 250 ms (when repeatrate is set to 25 ms). <<

Actually 500 ms if they&#39;re separate characters. It&#39;s key down for one clock time and up for one clock time, so two clock periods per character. If it&#39;s a repeating character ("a" rather than "a a a a a...") though, then there is really only one key-down and one key-up separated by some integral number of clock periods. The number of characters that would show up in NotePad (for example) is really a function of the system settings (which is undoubtedly locked to the system clock somewhere along the line). Windows creates a pseudo-autorepeat internally and there&#39;s nothing that you can do to control it from within the CM. Direct X itself only reports the keystate changes anyway.

>> Now what happens to my script during those 250 ms ? Will it still run every 25 ms ? Or will the script be frozen for 250 ms ? <<

Well, keeping in mind the caveats on timing mentioned above, the script will keep running.

>> If the script keeps running, what happens if I set CMS.B99 to FALSE before all chararcters are send ? Will the sequence finish and send all characters ? Or will it be interrupted and only a few chararcters will be send ? <<

Once you start the string, it will play to completion. If B99 is on for too short a time and gets missed the string won&#39;t start at all. If it does start, it will finish regardless of what B99 does. A "repeating" single key would stop right away, but if it&#39;s multiple characters they&#39;ll keep coming.

- Bob

The StickWorks
http://www.stickworks.com

LittleFlower
17th June 2004, 09:16 PM
Thanks Bob, for the great support.

The frame rate won&#39;t affect it, there&#39;s no way the CM can know what the FR is anyway

Understood. However it could be the case that the Windows scheduler doesn&#39;t allow your joystick driver to run unless UT releases the CPU ? In that case your driver might run only once in between each UT frame. I don&#39;t know enough about Windows and the WinXP scheduler to determine if that is true.

So I can assume that your driver is scheduled like a true driver (inside the kernel) and runs whenever needed, regardless of other user processes ? (I&#39;m sorry to be so curious. In a previous life I used to write realtime software myself). This is what I assume, but you also tell us that the time between script loops depends on priority of other processes. That would assume your driver runs in user space.

The timing will never be something that you can set your watch by.

Understood. I am not worried about a few milliseconds. But I am slightly worried about adding all the variation in time between loops. Suppose I want my script to wait for 0.5 second. I assume that each loop is 25 ms, so I program some homebrew counter and take action when it reaches 20 loops. However, if each loop would take 40 milliseconds, because of some delays, the overall waiting time would now be 0.8 milliseconds.

I would still think it would be a great addition to the CMS language if we could use timestamps. Very similar to the timers we already have in CMS. The only difference would be that the time is expressed in milliseconds, and not in clockticks. Even if the clock has the same impreciseness as before, the endresult will be more accurate. If a clocktick would take 40 ms in real time, in stead of the expected 25 ms, then after a number of clockticks after roughly 0.5 seconds the inaccuracy would still be only 15 ms. With clock ticks, the total time can be up to 40 * 20 = 800 ms, while with true timestamps, the total will be n * 40 up to 0.5 seconds, and then at the next tick the timestamp will expire. So at most 540 ms.

This might not be a big deal in flightsims, I don&#39;t know. But for FPS games, timing is pretty important.

I wouldn&#39;t make such a request if I knew it would be hard to implement. :) I don&#39;t know Windows very well. But in the RealTime OS I was programming in, there was always a clock (in milliseconds) mapped into the process memoryspace. Setting a timestamp would just be copying the number of milliseconds since bootup from the systemclock, adding the number of milliseconds, and storying it. Then at each loop you could just compare the timestamp to the systemclock. Very simple. I hope it is just as simple in WinXP. :P If not, please forgive my freshness.

Thanks for all the other explanations. Much appreciated.



[

Bob Church
18th June 2004, 02:27 AM
>> Thanks Bob, for the great support. <<

You&#39;re welcome!

>> Understood. However it could be the case that the Windows scheduler doesn&#39;t allow your joystick driver to run unless UT releases the CPU ? In that case your driver might run only once in between each UT frame. I don&#39;t know enough about Windows and the WinXP scheduler to determine if that is true.

So I can assume that your driver is scheduled like a true driver (inside the kernel) and runs whenever needed, regardless of other user processes ? (I&#39;m sorry to be so curious. In a previous life I used to write realtime software myself). This is what I assume, but you also tell us that the time between script loops depends on priority of other processes. That would assume your driver runs in user space. <<

It&#39;s all kernel, but even though kernel threads and user threads run in two different worlds, they&#39;re both subject to scheduling. There&#39;s actually a setting somewhere in XP that lets you adjust the the proportioning of Kernel Time to User Time to a degree. Anyway, UT can&#39;t hold on to the CPU indefinitely. 2K/XP does put a maximum time on how long a thread can run, I don&#39;t know if 98 did, but XP will preempt it eventually. You can get around that in the kernel by stopping the thread dispatcher, but if you do it for more than a few microseconds you&#39;ve got trouble with other processes, and first your thread has to be running. By then, if the timing is the problem, it&#39;s probably too late anyway. In any case, if all you do is run the driver UT probably isn&#39;t nearly as much fun. :)

Kernel threads do probably get better treatment than user threads, but the CM is running at about as high a priority now as you can use without causing problems elsewhere. The kernel may be running a lot of other threads and so you&#39;re still in contention with any of them that have the same priority as you do, and you&#39;re still going to wait for threads with a higher priority (interrupts, for example). Windows may also boost the priority on threads it thinks have had to wait too long and those can get in the way. They&#39;re subject to preemption, too, but the preemption time is out around 20 ms I think and so it&#39;s not much help with this problem. The stray thread can still shut you down for 20 ms or so.

>> Understood. I am not worried about a few milliseconds. But I am slightly worried about adding all the variation in time between loops. Suppose I want my script to wait for 0.5 second. I assume that each loop is 25 ms, so I program some homebrew counter and take action when it reaches 20 loops. However, if each loop would take 40 milliseconds, because of some delays, the overall waiting time would now be 0.8 milliseconds. <<

Well, there are actually two timers. The one that controls the regular real-time clock which is pretty slow when you look at it externally, 10-20 ms or so, and then there&#39;s the performance counter which uses the chips time stamp counter if it&#39;s there (it is on most CPUs, I think earlier AMDs and Celerons may have been lacking). If it&#39;s not, I think they just use the real-time clock. Anyway, if the TSC is available, it basically counts the machine cycles since the chip got power, 1 nanosecond resolution on a 1gHz machine, but you get the tick frequency, too, and so it&#39;s not too hard to get elapsed time since Windows start. I could set it up to give you a CMS variable with a 32-bit count of milliseconds since startup (or maybe script activation) without much trouble. You&#39;d have to deal with the time-stamping of the events in the script yourself, but the clock would be there. If you think that would be enough to do what you want, I could probably work it in with the next update. I&#39;m working on setting up a couple of new variables anyway so that piece of the scripting is up for a little modification already.

Would that do the trick do you think?

- Bob

The StickWorks
http://www.stickworks.com

LittleFlower
2nd July 2004, 03:58 PM
Hi Bob,

I&#39;m sorry for the long delay before my reply. I had misread your reply earlier, and wanted to take some time to prepare a proper answer. Of course RealLife got in the way. I reread your reply, and there is actually not anything extra I need to ask.


I could set it up to give you a CMS variable with a 32-bit count of milliseconds since startup (or maybe script activation) without much trouble. You&#39;d have to deal with the time-stamping of the events in the script yourself, but the clock would be there. If you think that would be enough to do what you want, I could probably work it in with the next update.[/b]

Yep !!! That&#39;s exactly what I&#39;d like to have ! With a 32-bit count of milliseconds since startup I can do my own timestamping indeed. I expect that would be sufficient to write scripts that have very accurate timing. I love it already.

And a 32-bit counter is enough to last 49 days. So there is no need to check for wrap around. (I used to work for cisco, and in the mid nineties there was a flurry of bugs where routing software stopped working after the router was up for 49 days. I guess before the mid nineties, routers were never up for 49 days in a row. :D). Because of that long timeframe, it doesn&#39;t matter whether the new clock started ticking at system boot time or at script startup.

Thanks !

Henk.

--
PS. My UT script is pretty complex now. But after I get the timing a bit better, I&#39;ll publish it for other CHP FPS players to benefit. I&#39;ll also make a UT2004 version, with one-button dodge-jumping.

Bob Church
3rd July 2004, 12:07 AM
>> Yep !!! That&#39;s exactly what I&#39;d like to have ! With a 32-bit count of milliseconds since startup I can do my own timestamping indeed. I expect that would be sufficient to write scripts that have very accurate timing. I love it already. <<

Okay. It doesn&#39;t look difficult. When you get a chance, drop me an email at:

sticky (at) stickworks (dot) com

so we can talk about it a little.

>> And a 32-bit counter is enough to last 49 days. So there is no need to check for wrap around. (I used to work for cisco, and in the mid nineties there was a flurry of bugs where routing software stopped working after the router was up for 49 days. I guess before the mid nineties, routers were never up for 49 days in a row. ). Because of that long timeframe, it doesn&#39;t matter whether the new clock started ticking at system boot time or at script startup. <<

I almost mentioned that but I don&#39;t think there&#39;s a version of Windows that will run for 49 days in a row anyway. A non-issue. :).

- Bob

LittleFlower
3rd July 2004, 02:34 AM
Thanks.
I&#39;ve just sent you an email.